OdeToCode IC Logo

Attribute Routes and Hierarchical Routing

Monday, August 12, 2013

As announced earlier this year, attribute routing will be a part of the next ASP.NET release. You can read more about the feature on the asp.net wiki, but here is a quick example to get the idea:   

public class ServerController : ApiController
{         
    [GET("api/server/{server}")]
    public IEnumerable<string> GetDatabaseNames(string server)
    {
        // ...
    }

    [GET("api/server/{server}/{database}")]
    public IEnumerable<string> GetCollectionNames(string server, string database)
    {
        // ...        
    }     
}

Attribute routing has been around for some time (you can use it today), and is invaluable for certain types of applications. While today’s default routing approach in ASP.NET MVC and Web API is easy and conventional, the  approach lacks the flexibility to make the hard scenarios easy. Scenarios like modeling hierarchical resources as in the above code, where the application wants to respond to api/server/localhost/ and api/server/localhost/accoutingdb. Other scenarios include creating controller actions with custom parameter names, and actions that can respond to multiple URIs.

Overall, the addition of attribute routing to the framework is a win.

However . . .

One of the benefits of attribute routing listed on the asp.net wiki is:

[Talking about conventional routing] –> The information about what URI to use to call into a controller is kept in a completely different file from the controller itself. A developer has to look at both the controller and the global route table in configuration to understand how to call into the controller.

An attribute-based approach solves all these problems by allowing you to configure how an action gets called right on the action itself. For most cases, this should improve usability and make Web APIs simpler to build and maintain.

I disagree!

I personally like centralized route configuration. Having routes defined in one place makes it easier to order and optimize the routes, and also think about URIs before thinking about an implementation (which is arguably more important for an API than a regular web site).

As a comparison, consider Darrel Miller’s ApiRouter, which also allows for flexibility and hierarchy in the routing rules (below is an excerpt for routing rules to mimic GitHub’s model). 

Add("issues", ri=> ri.To<IssuesController>());
Add("events", ri => ri.To<EventsController>());
Add("networks", 
  rn => rn.Add("{userid}", 
    ru => ru.Add("{repoid}", 
      rr => rr.To<NetworksController>())));

Add("gists",rg => rg.To<GistsController>()
  .Add("public",
     rp => rp.To<GistsController>(new { gistfilter = "public" }))
  .Add("starred", 
     rs => rs.To<GistsController>(new { gistfilter = "starred" }))
  .Add("{gistid}", 
      rgi => rgi.To<GistController>("justid")
  .Add("comments", 
      rc => rc.To<GistCommentsController>())

In the end, I believe using an approach like ApiRouter will lead to a routing configuration that is easier to understand, optimize, maintain, and troubleshoot.

I believe it will also will lead to a better API design, because attribute routing makes it easy to destroy the uniform interface for a resource and avoid looking at the bigger picture of how the URIs work together. 

Thoughts?