Named Routes To The Rescue

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/123 which 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=123 and /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

What others have said

Requesting Gravatar... Wim Bokkers Nov 21, 2010 3:50 PM
# re: Named Routes To The Rescue
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)?
Requesting Gravatar... Ant Nov 21, 2010 4:40 PM
# re: Named Routes To The Rescue
I’ve experienced that IRouteConstraint is a safer direction than to collude with magic strings.
Requesting Gravatar... Tim VanFosson Nov 21, 2010 9:31 PM
# re: Named Routes To The Rescue
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.
Requesting Gravatar... Tim VanFosson Nov 21, 2010 9:32 PM
# re: Named Routes To The Rescue
s/can/can't/ in the last sentence. Curse uneditable comments -- guess I'm spoiled by StackOverflow.
Requesting Gravatar... Martin Nov 21, 2010 9:37 PM
# re: Named Routes To The Rescue
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.
Requesting Gravatar... Paul Nov 22, 2010 2:39 AM
# re: Named Routes To The Rescue
This was a great article, but devs should always be unit testing their routes. Inbound and outbound routes!
Requesting Gravatar... Ron Muth Nov 22, 2010 3:04 AM
# re: Named Routes To The Rescue
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/...
Requesting Gravatar... Matt Tester Nov 22, 2010 3:13 AM
# re: Named Routes To The Rescue
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!
Requesting Gravatar... Chester Nov 22, 2010 6:18 AM
# re: Named Routes To The Rescue
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
Requesting Gravatar... Imran Nov 22, 2010 12:36 PM
# re: Named Routes To The Rescue
I will use a constraint in this case,
http://forums.asp.net/p/1589809/4028028.aspx
Requesting Gravatar... Slava Nov 22, 2010 8:31 PM
# re: Named Routes To The Rescue
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?
Requesting Gravatar... Mahesh Velaga Nov 23, 2010 2:24 PM
# re: Named Routes To The Rescue
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
Requesting Gravatar... shahzad Nov 25, 2010 5:53 PM
# re: Named Routes To The Rescue
hi.
i am new developer ... tell me that how i join microsoft.
Requesting Gravatar... Joe Reynolds Nov 27, 2010 12:50 PM
# re: Named Routes To The Rescue
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.
Requesting Gravatar... Øyvind Valland Nov 28, 2010 6:23 PM
# re: Named Routes To The Rescue
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.
Requesting Gravatar... SteveR Dec 01, 2010 7:08 PM
# re: Named Routes To The Rescue
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.)
Requesting Gravatar... haacked Dec 02, 2010 12:56 AM
# re: Named Routes To The Rescue
@SteveR depends on how you pronounce "Url". Many people pronounce it "Earl" and not "You-Are-El".
Requesting Gravatar... SteveR Dec 02, 2010 6:50 PM
# re: Named Routes To The Rescue
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.

What do you have to say?

(will show your gravatar)
Please add 1 and 8 and type the answer here: