This article is taken from the book ASP.NET MVC in Action from Manning Publications. One of the greatest aspects of ASP.NET MVC is its flexibility. Among other things, this gives us the capability to customize standard components. In this article, we'll take a look at how URL routing functions, and then explore how to enhance it to behave differently. For the book’s table of contents, the Author Forum, and other resources, go to http://manning.com/palermo/.
Extending URL Routing in ASP.NET MVC
Excerpted from
ASP.NET MVC in Action
Jeffrey Palermo, Ben Scheirman, and Jimmy Bogard
MEAP Release: February 2008
Softbound print: March 2009 (est.) | 275 pages
ISBN: 1933988622 The UrlRouteModule is an HttpModule and represents the entry point into the ASP.NET MVC Framework. This module examines each request, builds up the RouteData for the request, finds an appropriate IRouteHandler for the given route matched, and finally redirects the request to the IRouteHandler's IHttpHandler. Make sense?
Our default route looks like Listing 1. The MapRoute method is actually a simplified way of specifying routes. The same route can be specified with more detail, as is shown in Listing 2.
That third argument in Listing 2 is telling the framework which IRouteHandler to use for this route. We are using the built-in MvcRouteHandler that ships with the framework. By default we are using this class when using the MapRoute method. We can change this to be a custom route handler and take control in interesting ways.
An IRouteHandler's responsibility is to create an appropriate IHttpHandler to handle the request given the details of the request. This is a good place to change the way routing works, or perhaps to gain control extremely early in the request pipeline. The MvcRouteHandler simply constructs an MvcHandler to handle a request, passing it a RequestContext, which contains the RouteData and IHttpContext.
A quick example will help illustrate the need for a custom route handler. When starting out defining your routes, you'll sometimes run across errors. Let's assume you also have the route shown in Listing 3 defined.
This is a good example of a custom route that makes your URLs a lot more readable.
Now let's assume that we have another controller called Home. HomeController has an index action to show the start page.
We'd like the URL for the action in Listing 4 to look like /home/index. If we try this URL, we'll get a 404 error, as shown in figure 1. Why?
Figure 1 - This message doesn’t tell us much about what’s wrong. An action couldn’t be found on the controller, but which one?.
It's not apparent from that error message what the problem is. We certainly have a controller called HomeController, and it has an action method called Index(). If you dig deep and take a look at the routes we can deduce that this URL was picked up by the first route, /{conferenceKey}/{action}, which was not what we intended. We should be able to indentify quickly when we have a routing mismatch, so that we can fix it more quickly.
With lots of custom routes, it is easy for a URL to be caught by the wrong route. Wouldn't it be nice if we had a diagnostics tool to display which routes are being matched (and used) for quickly catching these types of errors?
What we’d like to do is have an extra query string parameter that we can tack on if we want to see the route information. The current route information is stored in an object called RouteData, which is available to us in the IRouteHandler interface. The route handler is also first to get control of the request, so it is a great place to intercept and alter the behavior for any route.
A route handler’s normal responsibility is to construct and hand-off the IHttpHandler that will handle this request. By default, this is MvcHandler. In our CustomRouteHandler we first check to see if the query string parameter is present (we do this with a simple regular expression on the URL query section). The OutputRouteDiagnostics method is shown in Listing 7.
1. Create an HTML table to display the route values for the current request.
2. Create an HTML table to display the routes
3. Green if it matches, Red if it doesn't
4. Places a chevron character (») next to the route selected for the request
This method outputs two tables, one for the current route data, and one for the routes in the system. Each route will return null for GetRouteData if the route doesn’t match the current request. The table is then colored to show which routes matched, and a little arrow indicates which route is the one in use for the current URL. The response is then ended to prevent any further rendering.
To finalize this change, we have to alter the current routes to use our new handler.
The end result (shown in Listing 2) is incredibly helpful. Let’s use the /home/index URL (that resulted in a 404 in Listing 1) but this time we’ll add the ?routeInfo to the query string. We can see in the route data table that the value “home” was picked up as a conference key, as shown in figure 2. The route table confirms that the conference key route was picked up first, since it matched.
Listing 2 – appending ?routeInfo gives us detailed information about the current request’s route. We can easily see now that the wrong route was chosen.
Now you can immediately tell that the current route used is not the one we intended. We can also tell whether or not other routes match this request by the color of the cell: Both of the rows are green. We now quickly identify the issue as a routing problem and can fix it accordingly. In this case, if we add constraints to the first route such that conferenceKey isn’t the same as one of our controllers, the problem is resolved. Remember that order matters! The first route matched is the one used.
Of course you wouldn’t want this information to be visible in a deployed application, so only use to aid your development. You could also build a switch that changes the routes to the CustomRouteHandler if you’re in debug mode, which would be a more automated solution. I’ll leave this as an exercise for the reader.
INSPIRED BY PHIL HAACK’S ROUTE DEBUGGER:
This example was inspired by Phil Haack’s route debugger that he posted on his blog when the ASP.NET MVC Framework was in Preview 2. It is a great example of what you can do with the information provided to you by the routing system. You can see his original example of this here:
http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx.
Another potential use of a custom route handler would be to append a specific identifier to the query string automatically. This could be useful in scenarios where you rely on cookie-less sessions or maybe you have a company identifier that limits what is displayed on the screen (your author has interfaced with such a framework). An IHttpHandler that would satisfy this requirement might look like this:
1. Force them to login
2. The URL contains a company key, so we can continue
In this example, every request must have a company key. The ProcessRequest method will not continue unless the URL contains this.
Hopefully you noticed how easy it was to extend the framework. Since most of the objects that you interact with are either interfaces or abstract base classes, it allows you to completely (or almost completely) substitute behavior for your own. It is in this level of flexibility where you will see the ASP.NET MVC Framework really shine.