Skip to content
This repository was archived by the owner on Nov 27, 2018. It is now read-only.

Retrieving Route Data from Virtual Path in MVC Router #823

Closed
Eilon opened this issue Sep 28, 2018 · 17 comments
Closed

Retrieving Route Data from Virtual Path in MVC Router #823

Eilon opened this issue Sep 28, 2018 · 17 comments
Assignees
Labels

Comments

@Eilon
Copy link
Contributor

Eilon commented Sep 28, 2018

From @knyzorg on Monday, 13 August 2018 20:01:26

I am working on an .NET Core MVC application which requires alternative controller/action names to be allowed. To accomplish this, I am using my own Router on a MapRoute:

app.UseMvc(routes =>
        {
            routes.Routes.Add(new CustomRouter(routes.DefaultHandler));
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

My custom router observes the requested controller and action, and based on it places a new value into the RouteData in the request:

public async Task RouteAsync(RouteContext context)
{
   [...]
   if (requestedAction == "fakeAction")
    context.RouteData.Values["action"] = "realAction";

However, to determine the value of the requestedAction, I am basically taking the requested path, splitting it and getting the value of it that way. This seems suboptimal.

What I would like to do would look something like this:

var rr = new RouteBuilder(app);
var myRoute = rr.MapRoute(...).Build();
var myRouteData = myRoute.GetRouteData(context);
myRouteData["action"] == "fakeAction";

Another solution to this problem which I would very much enjoy is if I could do the following:

   app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "something",
                template: "{controller=Home}/{action=Index}/{id?}");
            routes.Routes.Add(new CustomRouter(routes.DefaultHandler));
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

But NOT have my something route actually route anything and only serve as a way to define the RouteData for my CustomRouter.

Is either of these possible? I do not like the idea us uncleanly implementing existing functionality as it is a both a code smell and a potential maintenance difficulty in the future.

Copied from original issue: dotnet/aspnetcore#3427

@mkArtakMSFT
Copy link

Thanks for contacting us, @knyzorg.
@rynowak, what are your thoughts regarding this?

@rynowak
Copy link
Member

rynowak commented Sep 29, 2018

@knyzorg - at a high level what are you trying to accomplish?

@vezaynk
Copy link

vezaynk commented Sep 29, 2018

Really I just want to be able to have synonyms for controller and action names. I want /foo/bar to trigger the same code paths as /oof/rab if I so desire.

The reason for this is URL internationalization, I want to have urls look like en/hello/world and fr/salut/monde which map to the same page. I would leave MVC behind completely in favor of writing my own convention, but the convenience of having url templates and generating anchor tags automatically is too good to pass up.

Another need which I have, is for it not to break virtual paths (IIS).

Ideally, none of the synonyms would be hard-coded, I would rather be able to have some logic deciding what action (if any) the requested one is synonymous to during a request.

Anything like this possible?

@rynowak
Copy link
Member

rynowak commented Sep 29, 2018

Thanks, that's what I thought you might be asking 😀

to trigger the same code paths as /oof/rab if I so desire

Should this include views? If you have views, will this include different .cshtml files for each locale?

How do you want/expect link generation to work? Do you want something like:

Url.Action("World", "Hello", new { locale = "fr" }) -> /fr/salut/monde

OR

Url.Action("Monde", "Salut") -> /fre/salut/monde

I'm asking for clarification because there are a few difference schemes for how to implement something like this. The major pivot is whether you want to introduce another route value that represents the locale, or whether you want to localize the route values themselves.

@vezaynk
Copy link

vezaynk commented Sep 29, 2018

If the link generation could work by allowing me to specify the local myself, that would really be ideal if possible. So the first variant please.

Url.Action is basically the same thing as the <a> helper tag, right?

@rynowak
Copy link
Member

rynowak commented Sep 29, 2018

Url.Action is basically the same thing as the helper tag, right?

Yes, exactly.

@rynowak
Copy link
Member

rynowak commented Sep 29, 2018

Give me a bit, I'll whip up a sample of this.

@rynowak
Copy link
Member

rynowak commented Sep 30, 2018

@knyzorg - reading through your question again, I'm wondering if I forgot to point out something obvious.

You can change routes.DefaultHandler so that it points to your custom route. Then you'll be called with the route values on the way in, and when links are being generated.

Inside RouteAsync translate the values from the locale to whatever language you're writing code in, and then do the reverse in GetVirtualPath

routes.DefaultHandler = new TranslationRoute(routes.DefaultHandler);

Is this what you're looking for?


Another option that I've helped people used in the past is to use IActionModelConvention to 'multiply' actions - you can do all of the translations at startup time, and we'll handle all of the processing for you.

This won't be useful to you if you need to loop up the strings when each request is processed. Let me know if you're interested in this instead and I'll put together a sample.

@vezaynk
Copy link

vezaynk commented Oct 1, 2018

How would TranslationRoute be implemented?

@rynowak
Copy link
Member

rynowak commented Oct 1, 2018

Inside RouteAsync translate the values from the locale to whatever language you're writing code in, and then do the reverse in GetVirtualPath

@vezaynk
Copy link

vezaynk commented Oct 3, 2018

It's really going over my head. I don't know to implement a route by myself. I thought of extending the Route class to make a TranslationRoute but Route wants a third parameter in the constructor involving inline constraints which I never even heard of before.

This is my first project in .NET and I may need some hand holding.

@rynowak
Copy link
Member

rynowak commented Oct 3, 2018

OK thanks for the feedback, I'll get back to you in a bit with a more fleshed out sample.

@rynowak
Copy link
Member

rynowak commented Oct 5, 2018

Here's a sample: https://github.com/aspnet/Mvc/compare/rynowak/localization-routing?expand=1

There's a few things that are slightly non-obvious and clunky.

Creating the routes themselves is kinda gross because there are so many parameters that are rarely used https://github.com/aspnet/Mvc/compare/rynowak/localization-routing?expand=1#diff-cd035ee26336357cbade86282aad4a22R73

If you're using default values in the routes, you have 'translate' them in the defaults as well. https://github.com/aspnet/Mvc/compare/rynowak/localization-routing?expand=1#diff-cd035ee26336357cbade86282aad4a22R87

In the route, when you're doing link generation (GetVirtualPath) you will need to copy the 'language' from ambient values to values, in order for the correct route to be selected. https://github.com/aspnet/Mvc/compare/rynowak/localization-routing?expand=1#diff-42421a02d0010b370b9d53f0cee75762R31

If you're planning on using other localization features in ASP.NET Core then it would probably help to read over: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-2.1

In particular if you're using other MVC localization features then you will probably want to write an IResourceFilter that sets the current culture based on the language route value.

@vezaynk
Copy link

vezaynk commented Oct 5, 2018

It seems to be essentially what I was looking for. Thanks. I will try it out as soon as I can and will get back to you.

@rynowak
Copy link
Member

rynowak commented Oct 6, 2018

Great, I'm going to close this. Feel free to open another issue if you need something else.

@rynowak rynowak closed this as completed Oct 6, 2018
@vezaynk
Copy link

vezaynk commented Oct 10, 2018

Hey @rynowak, I plugged it in and it seems to be working. I also managed to understand why it works which is great.

However, why are you repeating the creation of routes while a single route seems to be working fine:

app.UseMvc(routes =>
             {
                 routes.Routes.Add(new TranslationRoute(
                    translations,
                    routes.DefaultHandler,
                    routeName: null,
                    routeTemplate: "{language=en}/{controller=Home}/{action=Index}/{id?}",
                    defaults: new RouteValueDictionary(new {  }),
                    constraints: null,
                    dataTokens: null,
                    inlineConstraintResolver: routes.ServiceProvider.GetRequiredService<IInlineConstraintResolver>()));
                 
             });

(Moved defaults language to the route template)

@rynowak
Copy link
Member

rynowak commented Oct 10, 2018

This would also work fine. My example (multiple routes) limits the number of languages that can match. In your example it would match any string with that first segment: /foobar/Home/Index and bind it to the language route value.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

4 participants