Url Routing is a cool feature that MVC depends upon. In this video, Scott Hanselman refers to the url routing as the “unsung hero” of MVC. He also demonstrated a Url Routing Debugger which will be discussed here. While I’m sure that there are quite a few things to url routing, in this post I’ll focus on what is probably a typical use of Url Routing.
Quite simply, in MVC the Url Routing’s job is to get you to the desired controller and invoke the correct method (action). Routing is performed in the Global.asax with a certain default provided by MVC templates. When you open a new ASP.NET MVC 3 project, Global.asax has a RegisterRoutes method which looks like this:
This represents the basics. The MapRoute method presented by default consists of a “route name” (which can be anything), a url pattern (that’s the important stuff) and defaults. The url specified as the default basically means that any url in the form of http://<server>/controller/action/id will be understood as the controller to redirect to, the method to invoke and a parameter to that action. For example, a url of http://<server>/scores/list/1 will be interpreted as “go to the scoresController class, run the ‘list’ method with an id of 1”. The defaults argument in this case represents a situation where some of the url’s items are missing. So, having a url of http://<server>, http://<server>/home, http://<server>/home/index or http://<server>/home/index/1 will all go to home controller and invoke the index method. The route uses the defaults to make up for missing url parameters. On the other hand, http://<server>/scores or http://<server>/home/edit will result at this stage in a 404 “resource cannot be found”, because there’s no scores controller, and no edit method in home controller (as long as we don’t add them, of course). One final thing regarding the default route: as long as you require only the most basic routing of http://<server>/controller/action and optional id, this route will be sufficient for your requests.
Adding additional routes means that you have custom route requirements which should be handled. These would probably be custom Urls to handle, which should direct the user to the different controllers and actions. In order to ensure that you’ve configured your routes correctly, it’s best to familiarize yourself with the Routing Debugger. In short, what this assembly does, is to render information summarizing the routes configured and how each url you test is being handled by those routes. In order to use the debugger, just download it from the blog above, extract it and reference the RouteDebugger.dll. After that you enter a single line of code in Application_Start (Line 8 below):
Any url typed in will now be reflected in the rendered html, with a “true”/”false” indication as to which route handles it. This makes it so much easier to understand routing. The picture below is a part of the information rendered concerning the routing for a default url (‘/’). It’s quite clear that the first row ignores the url, and that the second row of the table is the first route which corresponds to the url (and it is the route that will be invoked in this case). The last route is added automatically and will “catchall” the routes.
Now we can add our custom routes. For example, if we wish the user to be able to enter the following url: http://<server>/scores/list/123, this can be done like so (lines 5-9):
It’s important to notice that as our custom route refers to a hard coded controller (“scores”) and action (“list”), we had to supply the controller and action parameters in Line 8, which will instruct the route which controller and action will be invoked.
The route debugger will show:
Because our custom route was placed above the default route, we can see in the debugger that row 2 will be the first to capture our Url. This means that there’s an importance to the order routes are being set. Routes preceding other routes will take precedence even if they all answer to the same Url.
When you’re done with the route debugger, simply comment out the line and recompile in order to proceed with the development.
As for the Url’s arguments (e.g. “id”), the controller’s action can either handle these via method arguments, or by using the RouteData.Values dictionary, but you can’t have both at the same time or you’ll get a AmbiguousMatchException (“The current request for action … on controller type … is ambiguous between the following action methods”):
One more thing I think is worth mentioning is handling “greedy routes” and constraints. As you can easily observe, routes can easily become “greedy”, which means that they can pick up Urls that you basically intended for them to skip, in order for a different route to handle them. For example, the default route is quite greedy, as almost any url you enter will be picked up by it (whether or not you actually have a controller and action to handle them). You may also come across situations that you’d like the user to have several Urls which are very similar to each other, but they should be handled by different controllers or actions. For example, a Url can easily have an argument of a textual type and an integer type. So you might want different controllers or actions to handle them according to their data types. In all these cases you have the option of contraining a route using regular expressions. By constraining a route, routes will become less greedy and can be skipped over. As an example, lets assume that the Urls need to differ so that one of the parameters can be either a string or an integer, but with different controllers handling each one.
In lines 1 & 8 you can see examples of the Urls being handled. Note that we may omit line 12 and have the second route “greedier”, because we know that the first route will handle integers anyhow. Naturally, you can have different controllers assigned to handle each route.
Finally, if you insist on having actions (i.e. methods) of the same name with different overloads to handle the routes, you can place a single route to handle both integers and strings, and differentiate between the overloads using attributes, as shown here. I’m not sure why this is better than having constraints on your routes, but it’s always good to know your alternatives.