Named Routes To The Rescue

asp.net, asp.net mvc, code 0 comments suggest edit

The beginning of wisdom is to call things by their right names – Chinese Proverb

Routing in ASP.NET doesn’t require that you name your routes, and in many cases it works out great. When you want to generate an URL, you grab this bag of values you have lying around, hand it to the routing engine, and let it sort it all out.

nameless-routes

For example, suppose an application has the following two routes defined

routes.MapRoute(
    name: "Test",
    url: "code/p/{action}/{id}",
    defaults: new { controller = "Section", action = "Index", id = "" }
);

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = "" }
);

To generate a hyperlink to each route, you’d write the following code.

@Html.RouteLink("Test", new {controller="section", action="Index", id=123})

@Html.RouteLink("Default", new {controller="Home", action="Index", id=123})

Notice that these two method calls don’t specify which route to use to generate the links. They simply supply some route values and let ASP.NET Routing figure it out.

In this example, The first one generates a link to the URL /code/p/Index/123 and the second to /Home/Index/123which should match your expectations.

This is fine for these simple cases, but there are situations where this can bite you. ASP.NET 4 introduced the ability to use routing to route to a Web Form page.  Let’s suppose I add the following page route at the beginning of my list of routes so that the URL /static/url is handled by the page /aspx/SomePage.aspx.

routes.MapPageRoute("new", "static/url", "~/aspx/SomePage.aspx"); 

Note that I can’t put this route at the end of my list of routes because it would never match incoming requests since /static/url would match the default route. Adding it to the beginning of the list seems like the right thing to do here.

If you’re not using Web Forms, you still might run into a case like this if you use routing with a custom route handler, such as the one I blogged about a while ago (with source code). In that blog post, I showed how to use routing to route to standard IHttpHandler instances.

Seems like an innocent enough change, right? For incoming requests, this route will only match requests that exactly matches /static/url but no others, which is great. But if I look at my page, I’ll find that the two URLs I generated earlier are broken.

Now, the two URLs are/url?controller=section&action=Index&id=123and /static/url?controller=Home&action=Index&id=123.

WTF?!

This is running into a subtle behavior of routing which is admittedly somewhat of an edge case, but is something that people run into from time to time. In fact, I had to help Scott Hanselman with such an issue when he was preparing his Metaweblog example for his fantastic PDC talk (HD quality MP4).

Typically, when you generate a URL using routing, the route values you supply are used to “fill in” the URL parameters. In case you don’t remember, URL parameters are those placeholders within a route’s URL with the curly braces such as {controller} and {action}.

So when you have a route with the URL {controller}/{action}/{Id}, you’re expected to supply values for controller, action, and Id when generating a URL. During URL generation, you need to supply a route value for each URL parameter so that an URL can be generated. If every route parameter is supplied with a value, that route is considered a match for the purposes of URL generation. If you supply extra parameters above and beyond the URL parameters for the route, those extra values are appended to the generated URL as query string parameters.

In this case, since the new route I mapped doesn’t have any URL parameters, that route matches every URL generation attempt since technically, “a route value is supplied for each URL parameter.” It just so happens in this case there are no URL parameters. That’s why all my existing URLs are broken because every attempt to generate a URL now matches this new route.

There’s even more details I’ve glossed over having to do with how a route’s default values figure into URL generation. That’s a topic for another time, but it explains why you don’t run into this problem with routes to controller actions which have an URL without parameters.

This might seem like a big problem, but the fix is actually very simple. Use names for all your routes and always use the route name when generating URLs. Most of the time, letting routing sort out which route you want to use to generate an URL is really leaving it to chance. When generating an URL, you generally know exactly which route you want to link to, so you might as well specify it by name.

Also, by specifying the name of the route, you avoid ambiguities and may even get a bit of a performance improvement since the routing engine can go directly to the named route and attempt to use it for URL generation.

So in the sample above where I have code to generate the two links, the following change fixes the issue (I changed the code to use named parameters to make it clear what the change was).

@Html.RouteLink(
    linkText: "route: Test", 
    routeName: "test", 
    routeValues: new {controller="section", action="Index", id=123}
)

@Html.RouteLink(
    linkText: "route: Default", 
    routeName: "default", 
    routeValues: new {controller="Home", action="Index", id=123}
)

People’s fates are simplified by their names.  ~Elias Canetti

And the same goes for routing. Smile

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

18 responses

  1. Avatar for Wim Bokkers
    Wim Bokkers November 21st, 2010

    Great advice. I find the route matching process a bit confusing at times. With named routing I no longer need to understand the route matching process, which I think is a good thing. ;)
    Why not deprecate route matching in future versions of ASP.NET and show a warning when leaving out the route name (or make the route name a required parameter)?

  2. Avatar for Ant
    Ant November 21st, 2010

    I’ve experienced that IRouteConstraint is a safer direction than to collude with magic strings.

  3. Avatar for Tim VanFosson
    Tim VanFosson November 21st, 2010

    I'm in agreement with @Ant. Routing constraints seem to be a much better solution for routes that come before the default route in the routing scheme. I've generated named routes as well, but typically only to tie together a set of related actions spanning controllers or being reused from other workflow when performing a wizard, in effect giving me a virtual controller for the wizard. I'll grant you that I've never mixed webforms with MVC, so I can't speak to that but it seems like constraints would be useful there as well.

  4. Avatar for Tim VanFosson
    Tim VanFosson November 21st, 2010

    s/can/can't/ in the last sentence. Curse uneditable comments -- guess I'm spoiled by StackOverflow.

  5. Avatar for Martin
    Martin November 21st, 2010

    Hello Phil,
    i always do routing by route name, and i always specify the url as strict as possible.
    I have created some extension methods that can write the controller and action parameters (and more) for me in a secure strongly typed manner.
    For an example.
    routes.MapRouteToAction<HomeController>(RouteType.Home, "", x => x.Index());
    RouteType is an enum i use to make it easier to use when refering to some route. You could just use a string for the named route.
    It can automatically get attribute values on the action method and apply if you wish (HttpPost, HttpGet / AcceptVerbs).
    It can generate the url itself by looking at the controller name and the action method and use some convention like /ControllerName/ActionName, AreaName/ControllerName/ActionName, AreaName/ActionName, or you can specify it manually (because you probably dont want it to change just because you rename your action method).
    Its a little tricky when it comes to mapping something like /ControllerName/ActionName/{id}. I made a boolean option called allowOptionalParameters, and when set to true, it hooks up the parameters so MyActionMethod(int? id) will be something like
    routes.MapRouteInfoToAction<HomeController>(RouteType.SomeAction, "myaction", x => x.MyActionMethod(null));
    where null is the default value.
    Hope you understand my examples.

  6. Avatar for Paul
    Paul November 21st, 2010

    This was a great article, but devs should always be unit testing their routes. Inbound and outbound routes!

  7. Avatar for Ron Muth
    Ron Muth November 22nd, 2010

    Hi Phil,
    We had the same issues with using the default routes in mvc, so we had started using simply named routes, but that added more magic string to MVC and we are trying to get away from magic strings in our code. So we put together some helpers to create route names that are stored as constants and thus are fully accessible to intellisense. You can see my write up about it @ www.isi-net.com/...

  8. Avatar for Matt Tester
    Matt Tester November 22nd, 2010

    I use named routes so that I don't have to define the "controller" and "action" parameters when using it (in your final example, these can be removed from the routeValues). This helps when you end up refactoring the action name or even the controller!

  9. Avatar for Chester
    Chester November 22nd, 2010

    I'm using Asp.net 3.5 sp1 to do the url routing. I want to ignore a directory and all its sub directories. is this working?
    routes.Add("workarea/{*pathinfo}", new StopRoutingHandler());
    Thanks,
    Chester

  10. Avatar for Imran
    Imran November 22nd, 2010

    I will use a constraint in this case,
    http://forums.asp.net/p/1589809/4028028.aspx

  11. Avatar for Slava
    Slava November 22nd, 2010

    Thanks for sharing, good advice. Could we make route more compile time predictable. You could not check all of them with compiler, you should always make some tests yourself, maybe you have some ideas about that?

  12. Avatar for Mahesh Velaga
    Mahesh Velaga November 23rd, 2010

    I have encountered these kind of problems. Generally, I used to re-order the routes to handle the situation. But, you are right, there can come a situation when re-ordering gets painful as there is a risk of breaking some other links.
    Name Specification of routes in links FTW!
    Thanks

  13. Avatar for shahzad
    shahzad November 25th, 2010

    hi.
    i am new developer ... tell me that how i join microsoft.

  14. Avatar for Joe Reynolds
    Joe Reynolds November 27th, 2010

    What are the advantages of using Route Links or even Action Links over a plain old a href=
    I have a lot of working code where I have used Action Links and href links almost interchangeably. Sloppy perhaps, but it looks like the advantage of Route Link is to avoid route confusion such as your piece suggests.
    From a plain amount-of-typing standpoint, the href wins hands down.

  15. Avatar for &#216;yvind Valland
    &#216;yvind Valland November 28th, 2010

    I can see the benefits of named routes - but not using named routes gives you separation of concerns: When I put a link or a redirection in place I don't want to think about the URL itself, I just want to think about where that URL takes me, i.e. the action I end up on. Using named routes makes me have to think about which route entry I should use for any particular link, and that takes away a lot of the beauty of the routing engine in my opinion.
    Having said this I have banged my head against the wall many times when it comes to routes (mainly because I'm a bit thick) - and a named route would have fixed my problem. Still, I really do prefer not naming routes.

  16. Avatar for SteveR
    SteveR December 1st, 2010

    Sorry to be picky but "an URL" is wrong surely? Even if you expand the acronym, the first syllable is the "yoo" sound which is never preceded by "an" (e.g. "a union", "a uniform", "a unit" etc.)

  17. Avatar for haacked
    haacked December 1st, 2010

    @SteveR depends on how you pronounce "Url". Many people pronounce it "Earl" and not "You-Are-El".

  18. Avatar for SteveR
    SteveR December 2nd, 2010

    Perhaps, although I haven't heard that pronunciation very often here in the UK so I guess it depends where you're from. The whole "Sequel"/"Es-Que-El" thing comes to mind. I find "an URL" jarring to read, but maybe that's just me.