ASP.NET MVC Routing: Play Traffic Cop with Your Routes
As an ASP.NET MVC request is received, the routing sends the request to the proper controller. Today, we talk about how to use routing, some tips on using them effectively, and how to debug them easily.
Routing in ASP.NET MVC is one of the basic steps in the MVC Application Processing Pipeline. Since most developers don't see a need to adjust their routing, they leave it alone and carry on with their application.
But what happens when you need to make some adjustments to your MVC page structure? As Google has mentioned, you want to make sure you have SEO-friendly URLs. This means you may need to fix up your site so that Google, Bing, and/or Yahoo! can find your breadcrumb trail through your URL site structure.
NOTE: Download and Review the Lifecycle of an ASP.NET MVC 5 Application to understand what happens when routing occurs (PDF).
Routing Basics
I know I mentioned that I consider UrlHelpers as a table of contents for your application, but routing is also a way to find out how the application is structured. If you are just getting into an ASP.NET MVC application for the first time with a client, I would definitely recommend that you ask them where the UrlHelpers and Routing are located in the application.
If you get a blank stare or they say they just use ActionLinks throughout their application, I would start to worry.
Ok, now I'm referring to that lifecycle document you should've downloaded from above.
When a web request first comes in to your application, it reads the Global.asax.cs class to setup the routes for your application, then heads to the App_Start\RouteConfig.cs file, and registers the routes defined in the static class. It only happens once.
If you don't have a need to modify your routes, you can leave them alone.
Here is the default route with an explanation below:
Default Route
routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } );
The MapRoute method defines the following:
- Name - The Name of the Route (call it anything you want...within reason for fear of your fellow developers yelling at you) ;-)
- URL - The format of your URL
- Defaults - Sets the default controller, action, and id if nothing was passed in. If just a the controller was passed in, the default action would be Index on that controller name.
- Namespaces (optional) - If you are using areas or more than one namespace, you would add the namespace as a fourth parameter like this:
namespaces: new string[] { "DanylkoWeb.Controllers"}
- Constraints (optional) - You can define only certain criteria to come through your routes using constraints. As an example, you could write your own constraint to allow only Chrome users (check the user-agent) when they come to your site and send other browser users away (Yeaahhh...that would be bad).
Once the {controller}, {action}, and {id} are identified, those values are matched to the signature of the controller, action, and other parameters.
Notice I didn't say 'id'? Id can be optional. Heck, it can even be a different parameter signature in the controller.
So if we had the following URL:
http://www.danylkoweb.com/Blog/Detail/15
The route would breakdown the URL into the following route values:
- 'Blog' is the controller name
- 'Detail' is the name of the method in the controller
- '15' would be the id passed into the method name, Detail.
The general rule is that the route defaults MUST match the action's parameters exactly. It is case sensitive.
"But I want easy-to-read URLs!"
Let's look at a harder example. How about DanylkoWeb, to be more specific?
Notice the URL at the top:
http://www.danylkoweb.com/Blog/aspnet-mvc-routing-play-traffic-cop-with-your-routes-90
But how do you know which action it goes to? It sure doesn't go to the "aspnet-mvc-routing-play-traffic-cop-with-your-routes-90" method, does it?
No, it doesn't. Here the trick and the route to pull it off:
routes.MapRoute( name: "BlogDetail", url: "Blog/{title}-{id}", defaults: new { controller = "Blog", action = "Detail", title = String.Empty, id = UrlParameter.Optional }, namespaces: new string[] { "DanylkoWeb.Controllers" } );
Let's take it step by step.
Everything looks the same regarding the controller name (Blog), but we have a different parameter type in the URL. Remember when I said you can have different parameters in your URL? This is what I'm talking about when I mentioned it above.
The key here is the last dashed word in the URL. The ID is the last number after the last dash, which is 90. That "id" is passed into the detail method in your Blog controller:
public ActionResult Detail(string id) { // LoadById(id) - load post id 90 .
. }
If you want to include the title in the method signature along with the id, just add "string title," in the method and act on it.
Using Large URLs
One project I worked on in the past was a link directory structure. It was a recursive database table with a lot of locations. A sample URL looked like this:
http://www.domain.com/LinkDirectory/NorthAmerica/UnitedStates/Ohio/Columbus
So how would you break this down to a route?
You would create a route like this:
routes.MapRoute( "LinkData", // Route name "LinkDirectory/{*data}", // URL with parameters new { controller = "LinkDirectory", action = "Category" } );
Ok, see the *data?
The asterisk is a catch-all parameter. It will match and pass /NorthAmerica/UnitedStates/Ohio/Columbus to the LinkDirectory.Category method. As before, make sure the category action method has a parameter (data) that matches the MapRoute parameter name (data).
The method would look like this:
public ActionResult Category(string data, int? page) { // data = "/NorthAmerica/UnitedStates/Ohio/Columbus" }
At this point, you can now parse the URL and load the data.
Debugging Routes
If you have a large application that contains many routes, it may be difficult to track down each and every route coming in to your application. It can be quite maddening!
Here are a few tips to make debugging your routes easier so you don't lose your sanity. ;-)
- If you have a large number of routes registered in your RouteConfig.cs, you may want to take a step back and make a REAL table of contents for your application. How should your site be structured?
// /Blog // /Blog/title-name-A9 // /About/ // /Contact/ // /FAQ/ // . // . // .
At first glance, I see only two routes for these URLs...and they were covered in this post: The one route named "Default" and the other one named "BlogDetail."
I have seen others create Routes for each and every request coming in. This table-of-contents method provides an at-a-glance look at how your application is structured and shows you all available URLs for your site. If you don't have any goofy URL structures, I would keep to the Default route. - While I've never hit the maximum number of routes, I've heard that the more routes you add to your application, the slower it will perform. Try to keep your routes to a minimum and make sure you know that every route is being used.
- Another tip has to do with adding more band-aids on top of more band-aids. As I mentioned in Getting Started With CSS, instead of adding more routes configs on top of each other to figure out the issue, strip every route out (or comment them out) to the very essentials (maybe one "Default" route) and start adding one route at a time to determine your culprit.
- Route Debugging Tools
- Glimpse - One great tool made especially for ASP.NET MVC applications. It has a lot of data to help you diagnose and test sections of your code (just don't put it on your production box, ok?)
- MVC Route Visualizer - Exactly what it says: it displays a route based on a URL you enter.
- Route Debugger Tool (NuGet) - Since Phil Haack created this a while back, someone packaged it up on NuGet making it easier to install and use.
Conclusion
ASP.NET MVC Routing becomes extremely important when you expect a URL to go to a certain controller and it diverts the request to another page altogether.
If you understand the basics and keep your routes simple, you may still be able to keep your sanity (for now) ;-)
References:
Do you have any other Route Debugging tips? Post them below!