Click here to monitor SSC
  • Av rating:
  • Total votes: 14
  • Total comments: 3
Dino Esposito

Attribute Routing in Web API v2

21 August 2013

Attribute routing solves a lot of the problems of classic ASP.NET routing, which can get ungainly when there are many  route handlers, or you have several  REST services. Dino shows how to enable and use attribute routing  in ASP.NET MVC

Today we’re all used to software services exposed over HTTP. We also expect these services to be available as plain HTTP callable endpoints: no proxies and no special API to sit in between as a mediator. This was not always the case, as you may recall.

When WCF Came on Stage

In the Microsoft stack, at some point we’ve been given WCF as the framework to use to expose externally callable endpoints. WCF was a great platform: extensible, flexible, scalable and probably close to perfection. A few years later, though, some people raised the point that maybe WCF was a bit over-engineered and that what had to be made easier and faster for WCF to meet actual needs  was just exposing HTTP callable endpoints. WCF was then topped with a long list of additional frameworks and starter kits to make programming HTTP endpoints easier. But easier coding doesn’t necessarily mean faster code.

ASP.NET MVC was particularly welcome also because it features controllers that can return JSON or XML. In a nutshell, in ASP.NET MVC you can have HTTP services at sole cost of adding a new controller class or a specific method to an existing controller class. This has always worked since version 1 of the framework. Controllers are much easier to set up than a WCF service and run as fast as reasonably possible. So WCF was quickly restricted to where the transportation had to be TCP or MSMQ, but not so much with HTTP.

Then It Came Web API

Problem solved? You bet! Even though ASP.NET MVC works very well, it still leaves something to be desired. ASP.NET MVC is tied closely to the ASP.NET framework and IIS. Web API came out to turn the good things in ASP.NET MVC into a new framework for HTTP services. The Web API framework relies on a different runtime environment that is totally separated from that of ASP.NET MVC. The runtime environment is inevitably largely inspired by ASP.NET MVC but, overall, it looks more straight-to-the-point as it is expected to only serve data and not markup.

Web API is close to its version 2 release. Version 2 is an evolutionary new release that enables and simplifies a few new features in Web API that were already available to ASP.NET MVC web developers. An example is external authentication now available through services such as Facebook and Google; another example is support for Cross-Origin Resource Sharing (CORS). Yet another example, and probably the most relevant, is attribute routing.

From a technical perspective, attribute routing is nothing special. Overall, it is a powerful feature that is relatively easy to explain and understand. However, the most interesting aspect of attribute routing in Web API is the reason why it exists and the original ideas it is based on. Let’s just start from here.

Routing and URL Templates

Attribute routing is not a feature that Web API inherits from ASP.NET MVC. Quite the reverse, attribute routing comes from old-fashioned WCF Web HTTP binding. Routing has always been a constituent part of ASP.NET MVC; attribute routing, instead, is a new variation of it.

Classic routing, which is still available in ASP.NET MVC 5 and Web API 2, is based on conventions. In global.asax, at the startup of the application, you populate a system dictionary with routes and let the system live with all of them. Any time a request comes in, the URL is matched against the template of registered routes. If a match is found, the appropriate controller and action methods to serve the request are determined from the template. If not, the request is denied and the result is usually a 404 message. The code below is taken from the default Visual Studio ASP.NET MVC project and shows the suggested way to register routes. The RegisterRoutes method is invoked from within Application_Start in global.asax

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

As a side-note, It also makes it easier for testing purposes to have a method prototyped just as RegisterRoutes. In a test you can call RegisterRoutes by passing a fake dictionary and then use the fake dictionary in the assertions.

There is another aspect, called route recognition, that is significant for the evolution towards attribute routing. Route recognition works on a first-match basis. The most immediate consequence of first-match is that the order in which routes are registered is essential. Most specific routes should appear first in the list; catch-all and most generic routes should go to the bottom of the list.

Why is this so significant? Well, in large applications, or even in medium-sized applications with a strong REST flavor, the number of routes may be quite large: and could easily be in the order of hundreds. If you’re a REST fanatic or if you’re just fussy about the URLs that your web application should support, then you may quickly find that classic routing becomes a bit overwhelming to handle. It can definitely become hard to determine the right order of more than 200 routes and you may find that infinite loops around regression are detected during automated tests or notified by users and testers.

Attribute routing, which is coming in ASP.NET MVC 5 and Web API 2, offers a smoother way to handle routing. This is especially true when you want to force a particular syntax across all pages and, more likely, all the objects you expose.

The Downside of Classic Routes

A route is a plain parameterized string. You use names in curly brackets to define parameters and concatenate numbers, static text and parameters in a sequence that ultimately determines the URL. A route is always the same thing whether you call it a classic ASP.NET MVC-style route or an attribute route. The difference is all in how you use it and where you define it.

A positive aspect of classic routes is that they are defined in a single place and work the same way regardless of the controller. Overall, you deal with one simple rule: any time a request is placed that matches any of the routes it is picked up. In addition, you can use constraints to restrict values in route parameters. So far so good. Now consider the following common example:

orders/show/1

The purpose of the URL is fairly obvious: show me the order with an ID of 1. However, in doing so you are accepting a couple of conventions. First, you must have a class named OrdersController. Second, and more importantly, the OrdersController class must have a public method named Show which accepts an integer. In addition, you must find a way to handle the case in which the integer—the ID—is not provided but for some reasons the resulting URL is matched to the same route.

Among other things, existing conventions open up to the risk of having non-action public methods on controller class. If you happen to have a public method that is not supposed to be callable from the outside you must explicitly opt it out of using the NonAction attribute, otherwise it will be processed anyway, thereby creating a potential security hole.

There’s a way for developers to hide the name of the method from the URL. It requires a custom route handler, as below.

public class OrdersRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        // Analyzes the actual URL being mapped to the route. If so,
        // programmatically determines controller/action
        if (requestContext.HttpContext.Request.Url.AbsolutePath.StartsWith("/orders/show/"))
        {
            requestContext.RouteData.Values["controller"] = "orders";
            requestContext.RouteData.Values["action"] = "display";
        }
        return new MvcHandler(requestContext);
    }
}

Within a custom route handler you can decide just about everything, including the real name of controller to be used and the action method.

Unfortunately, a custom route handler is just added to the list of routes and you must find the right place for it. It’s no big deal as long as you have 10 routes; it starts getting a problem when you want to exercise a close control over too many individual routes. Here’s the code you need in order to add a route with a custom handler.

var optionals = new RouteValueDictionary { 
     { "controller", UrlParameter.Optional }, 
     { "action", UrlParameter.Optional } };
routes.Add("CustomOrdersRoute",
            new Route("customordersroute", optionals, new OrdersRouteHandler()));

Worse yet, when you have a long list of routes, each of which expresses a specific URL template, it is not unusual that you end up with many route handlers—nearly one per route. You understand that this is not particularly satisfactory. In the end, classic routes don’t lack any functionality; they’re simply hard to use when you’re fussy about URL templates. This is quite common in a REST-intensive design. Enter attribute routing.

Attribute Routing in Action

From the technical perspective, attribute routes are as easy as conventional routes except that they work better in a REST design. As their name may suggest, attribute routing is all about having a route attached, as an attribute, to a specific action method.

[WebGet(UriTemplate="orders/{id}/show")]
Order GetOrderById(int id);

The code sets the method GetOrderById to be available over a HTTP GET call only if the URL template matches the specified pattern. The route parameter—the orderId token—must match one of the parameters defined in the method’s signature. There are a few more details to be discussed, but the gist of attribute routes is all here.

There’s a clear resemblance with the now old-fashioned WCF Web HTTP programming model and the WebGet attribute in particular.

[WebGet(UriTemplate="orders/{id}/show")]Order GetOrderById(int id);

As I see things, attribute routing is just the revamped and extended version of routing as implemented in the WCF Web HTTP programming model. And, more importantly, it fits a specific requirement that programmers currently have.

Enabling Attribute Routing

Attribute routing is not enabled by default, but it can work side-by-side with conventional routing. Here’s the standard way to enable it:

public static class WebApi2Config
{
    public static void Setup(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();
    }
}

You don’t have to use a config class with static methods in pure MVC4 style; what really matters is that you invoke the new MapHttpAttributeRoutes method on the HttpConfiguration class. In case you intend to use the two types of routing together, it is preferable you give precedence to attribute routing. This means you call MapHttpAttributeRoutes before you start adding global routes.

You can define a route via attribute for each method you like and also filter on the HTTP method used to call the action. In Web API 2, attribute classes like HttpGet, HttpPost, HttpPut and all others have been extended with an overload that accept a route URL template. If you like it better, you can also use the AcceptVerbs attribute, where the first parameter indicates the method name and the second parameter sets the route.

[AcceptVerbs("GET", "orders/{id}/show")]

You can use AcceptVerbs also for unsupported HTTP methods or perhaps WebDAV methods.

Attribute Route Constraints

Attribute routing also supports constraints on parameters using a slightly different syntax than classic routing. The API has a list of predefined constraints such as int, bool, alpha, min, max, length, minlength, range. Here’s how to use them:

[HttpGet("orders/{id:int}/show"]

The goal of constraints is straightforward. For more details, you can check out some documentation at http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2.

You can concatenate as many constraints as you wish and can create custom constraints as well.

[HttpGet("orders/{id:int:range(1, 100)}/show"]

To create a custom constraint you create a class that implements the IHttpRouteConstraint interface and use the following code to register it:

var resolver = new DefaultInlineConstraintResolver();
resolver.ConstraintMap.Add("custom", typeof(YourCustomConstraint));
config.MapHttpAttributeRoutes(new HttpRouteBuilder(resolver));

The interface IHttpRouteConstraint has a single method named Match. Finally, you should note that each controller class can be decorated with a route prefix string in order to minimize the route string. When a route prefix is defined it is appended to any attribute routes before parsing. Here’s an example:

[RoutePrefix("orders")]
public class OrdersController : ApiController
{
    [HttpGet("{id:int:range(1, 100)}/show }")]  
    public Order GetOrderById(int id) 
    { 
      :
    }
    :
}

You can have multiple route prefixes for a controller; when this happens each route is evaluated individually in the order it appears until a match is found.

Summary

At every new build of ASP.NET MVC 5 and Web API 2, attribute routing gains in importance. It is likely to also end up making a debut in plain ASP.NET MVC. Attribute routing is not the type of feature that alone will push you to use Web API. However, it’s just one of those features that’s really nice to have.

Dino Esposito

Author profile:

A long-time trainer and consultant, Dino is the author of many popular books for Microsoft Press for .NET developers.including “Architecting Mobile Solutions for the Enterprise“ and “Programming ASP.NET MVC” both for Microsoft Press. CTO of Crionet, a firm specializing in Web-based and mobile solutions for sport events across Europe (http://www.crionet.com), at the moment Dino is also technical evangelist for JetBrains, where he focuses on Android and Kotlin development, and member of the team that manages WURFL—the database of mobile devices used by organizations such as Google and Facebook. Follow Dino through his blog at http://software2cents.wordpress.com or at http://twitter.com/despos

Search for other articles by Dino Esposito

Rate this article:   Avg rating: from a total of 14 votes.


Poor

OK

Good

Great

Must read
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: Quesion
Posted by: Ahmed Moosa (not signed in)
Posted on: Wednesday, September 04, 2013 at 3:01 AM
Message: Thanks for Sharing , but, What about Areas ?

Subject: THX
Posted by: longen (view profile)
Posted on: Monday, September 09, 2013 at 11:07 AM
Message: Hello Dino, You are still Guru!

Subject: Attribute routing rocks
Posted by: JoNSmith (view profile)
Posted on: Friday, September 13, 2013 at 7:05 AM
Message: Hi Dino,

As I commented in one of your previous posts "Building a Public HTTP API for Data", which I found very useful when deciding how to implement a data API. Basically after some trials I when with the NuGet package AttributeRouting (see http://attributerouting.net/), which worked with MVC4.

Using attribute routing has made providing a data API really clear and easy, both for the developer and the consumer. Glad to see it has made it into MVC5.

Thanks for a run though of how it works.

 

Top Rated

Acceptance Testing with FitNesse: Multiplicities and Comparisons
 FitNesse is one of the most popular tools for unit testing since it is designed with a Wiki-style... Read more...

Acceptance Testing with FitNesse: Symbols, Variables and Code-behind Styles
 Although FitNesse can be used as a generic automated testing tool for both applications and databases,... Read more...

Acceptance Testing with FitNesse: Documentation and Infrastructure
 FitNesse is a popular general-purpose wiki-based framework for writing acceptance tests for software... Read more...

TortoiseSVN and Subversion Cookbook Part 11: Subversion and Oracle
 It is only recently that the tools have existed to make source-control easy for database developers.... Read more...

TortoiseSVN and Subversion Cookbook Part 10: Extending the reach of Subversion
 Subversion provides a good way of source-controlling a database, but many operations are best done from... Read more...

Most Viewed

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Calling Cross Domain Web Services in AJAX
 The latest craze for mashups involves making cross-domain calls to Web Services from APIs made publicly... Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Why Join

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.