The importance of the order of routes in ASP.NET MVC
Last week I had the pleasure of listening to a presentation from Scott Guthrie here in Stockholm, http://weblogs.asp.net/scottgu/archive/2009/12/06/my-presentations-in-europe-december-2009.aspx. One of the most interesting parts of the all day event was his talk about ASP.NET MVC 2. Yesterday I had some time off and started to try things out using Visual Studio 2010 Beta 2 and the ASP.NET MVC 2 template. It was running smoothly for a while until I bumped into a problem I recognized from earlier. I didn’t remember the solution right away, but soon I figured it out what was the key point in making the routes, registered in Global.asax, work properly.
The order when adding the routes using the MapRoute method on a RouteCollection object is important to avoid getting a 404 page when browsing to pages that are supposed to work. I will present the problem and the solution for it below.
The problem
Let’s say that you want to present a list of customers, from either the Northwind database or the AdventureWorks database. This is a straight forward easy thing to accomplish by adding a controller, CustomersController, and an Index action to this. This action will return a list of customers from the database, where the customer type depends on the database connection in the project. I decided to use ADO.NET Entity Data Model, but that is not relevant for the problem stated here.
The action in the controller looks like this:
public class CustomersController : Controller
{
MvcApplication3.Models.AdventureWorksLT2008R2Entities aw =
new Models.AdventureWorksLT2008R2Entities();
//
// GET: /Customers/
public ActionResult Index()
{
var customers = aw.Customer.ToList();
return View(customers);
}
}
Of course there’s a View for presenting the list of customers using a ul-li list and the hyper link control created by using the Html.RouteLink method:
Html.RouteLink(
item.FirstName,
"Customer-details",
new { Controller = "Customers", Action = "Details", customerID = item.CustomerID })
To reach this list of customers, let’s add a route to the Global.asax.cs RegisterRoutes method:
routes.MapRoute(
"Customer-list",
"Customers",
new { controller = "Customers", action = "Index" });
This route will make it possible to reach the list of customers through
http://www.yourdomain.org/Customers
Browsing to this URL will trigger the CustomersController action Index shown above. The hyper link control created above using the Html.RouteLink will create a list of customers, hyper linked like this:
http://www.yourdomain.org/Customers/10
where the ’10′ is the ID of the customer clicked.
As seen above the RouteLink method will use a route called “Customer-details”, so let’s define that one in Global.asax.cs:
routes.MapRoute(
"Customer-details",
"Customers/{customerID}",
new {
controller = "Customers",
action = "Details",
customerID = (int?)null });
This route will trigger the CustomersController action Details, but only if the route is added in the correct order. This is where the things started to go wrong for me, just like before when I bumped into the 404 page problem earlier.
Here goes the Details action in the CustomersController:
public ActionResult Details(int? customerID)
{
var customers = from p in aw.Customer where p.CustomerID == customerID select p;
return View(customers.FirstOrDefault());
}
The view added for this action is a standard details view in the ASP.NET MVC Framework.
Sadly this action is not triggered, but why? Here goes the faulty RegisterRoutes in the Global.asax.cs:
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Route name, URL with parameters, Parameter defaults
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {
controller = "Home",
action = "Index",
id = "" }
);
routes.MapRoute(
"Customer-list",
"Customers",
new { controller = "Customers", action = "Index" }
);
routes.MapRoute(
"Customer-details",
"Customers/{customerID}",
new {
controller = "Customers",
action = "Details",
customerID = (int?)null }
);
}
Some notes on the code above:
On line 8 the route added by the project template is added. On line 17 the customers list route is added and on line 23 the customer details route is added.
This RegisterRoutes method will cause a 404 page when browsing to
http://www.yourdomain.org/Customers/[customerID].
This is annoying!
The solution
The solution to this problem is really really simple. It is a one sentence description, so here goes:
Move the Default route added on line 8 in the listing above to be the last thing in the RegisterRoutes method.
That’s it! The Details action in the CustomersController will be properly trigged and the details for the selected customer will be presented. The RegisterRoutes method will then look like this, making the customer details action work:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//
//
// Moved down (*)
//
// routes.MapRoute(
// "Default",
// "{controller}/{action}/{id}",
// new {
// controller = "Home",
// action = "Index",
// id = "" }
//);
//
//
routes.MapRoute(
"Customer-list",
"Customers",
new { controller = "Customers", action = "Index" }
);
routes.MapRoute(
"Customer-details",
"Customers/{customerID}",
new {
controller = "Customers",
action = "Details",
customerID = (int?)null }
);
// Moved from (*) to here
// Route name, URL with parameters, Parameter defaults
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {
controller = "Home",
action = "Index",
id = "" }
);
}
Great tutorial! Definitely a common problem out there!!
fredrik
10 Dec 09 at 12:56
Thanks! I realized that since I bumped into the same thing for the second time in a short period of time I just had to NOT make it a third time…
Fredde
10 Dec 09 at 13:18
Hi
Thanks the info, the problem I am having is if I start using AJAX in my page, unless the Default route is at the top, the ajax does not work correctly.
help would be appreciated.
Thanks
Adil
17 Jun 10 at 12:28