ASP.NET MVC attribute routing

As we start to add more pages to our ASP.NET MVC section of our application, I’ve found that the MVC routing table in version 4 is somewhat constraining. I’m not alone, it seems others find it awkward in doing more intelligent handling of routes.

Problem

Our site has customers, and we have different services for these customers. I’ve partitioned everything customer-related into it’s own area, so that the base URL for it is /Customer/

We use integer account ID’s from the database as our primary key, so logically if I want to refer to a specific customer I prefer the URL

/Customer/123

as our root. If we followed the standard MVC default, which would be

/Customer/Home/Index/123

Ugh. That just sounds long-winded and over-complicated.

So for example, the home page should be

/Customer/123/Home

The default ASP.NET route won’t work for this of course: /Customer/{controller}/{action}/{id} will interpret this as a controller called 123 and an action Home, with no ID. Easy enough to change the routing as we have until now:

/Customer/{customerID}/{controller}/{action}

First problem with this approach is that customerID is now a required value: what if I want to create a search page, to find a customer? I don’t have a customerID in this context, but it’s still customer related. What I’d like is a URL like

/Customer/Search

While this is possible in the MVC4 routing engine, it leads to a longer and longer set of routes to be registered in our RegisterRoutes method. What we actually want to specific is some kind of pattern matching a la regex, e.g.

/Customer/{CustomerID:\d+}/{controller}/{action}
/Customer/{controller:[^\d*]}/{action}

Attribute Based Routing

Alas the current MVC4 routing engine does not support this. However there is an alternative that it seems the ASP.NET team is adopting to appear in MVC 5: attribute based routing(ABR).

Installing is a breeze: Install the Nuget package it adds a setup file to the App_Start folder which loads the configuration option. That’s it.

Each controller/action can then decorated with attributes which define the routing. Using my example above, all my controllers live in the Customer area. However, I generally want to prefix each route/action with the customerID. In ABR, I can define the area and a prefix at the controller level:

    [RouteArea("Customer")]
    [RoutePrefix("{CustomerID:int}/Issue")]
    public class IssueController : Controller

In the above sample, the URL

~/Customer/{CustomerID}/Issue

is now the base URL for all actions on this controller. ABR will extract and parse the customer ID as an integer from the URL, and I can use it directly in my action methods, e.g.

        [GET("")]
        [GET("List")]
        public ActionResult List(int CustomerID)
        {
            return View();
        }

The List action works with a blank or “List” URL part, so either of the following routes will map to it:

~/Customer/{CustomerID}/Issue

~/Customer/{CustomerID}/Issue/List

No More IDs

When I refer to a specific Issue by ID, I can avoid the generic ‘{ID}’ we get in the old routing engine:

        // GET: /Customer/{CustomerID}/Issue/{IssueID}
        [GET("{IssueID:int}")]
        public ActionResult Index(int CustomerID, int IssueID)
        {

Now let’s say I want my search page, which does not have a customer ID:

~/Customer/Search

We can define a Search controller by dropping the CustomerID part:

    [RouteArea("Customer")]
    [RoutePrefix("Search")]
    public class SearchController : Controller
    {

This will map to the URL

~/Customer/Search

I won’t go into the ABR specification and all the options it provides, as the library is well documented and logical.

A big thanks to Tim McCall for his work!