Web API Attribute Routing

Web API Attribute Routing

Related: "Web API: Getting Off the Ground"

ASP.NET Web API relies on the concept of routes to determine mappings of incoming HTTP requests to controllers and actions. Web API does this through a global routing table—as one adds functionality to a project one must become very familiar with WebApiConfig.cs, where the routes are defined. One must constantly update the routing table as the API grows and changes, always keeping in mind that the controllers and actions are located separately from the routing definitions except through convention.

This has some advantages: routes are defined in one place, apply to every controller, and help enforce the conventions. Convention-based routing, however, makes certain scenarios that are common in RESTful APIs hard to support. Additionally, the conventions can result in conflicts in the routing table, matching incorrect actions.

With ASP.NET Web API 2, Microsoft is introducing a new set of routing specification capabilities, dubbed attribute routing, that aim to solve these problems, making it easier to build and maintain RESTful APIs using Web API. These same routing changes are also available to ASP.NET MVC, as MVC and Web API share many of the same technologies.

Related: "Microsoft's Web API Framework: Bridging the Divide Between Web Forms and ASP.NET MVC"

Attribute Routing in a Nutshell

For the sake of simplicity, you'll see dummy implementations where the methods return either default values or assume the actual work is going to be delegated to a different method. Here's an example shows how routes are traditionally defined:

  config.Routes.MapHttpRoute(

                name: "DefaultApi",

                routeTemplate: "api/{controller}/{id}",

                defaults: new { id = RouteParameter.Optional }

            );

Instead, attribute routing lets controllers and actions be decorated with route information. Here's a trivial example:
 

  public class CustomerController : ApiController

    {

        [Route("customer/{id}")]

        public string Get(int id)

        {

            return "value";

        }

    }

Here's a more complex example that shows a scenario in which routing would be oblique:

    public class BooksController : ApiController

    {

        [Route("author/{authorId}/books")]

        public IEnumerable<Book> GetByAuthor(int authodId){

            return GetBooksByAuthor(authorId);

        }

        [Route("publishers/{publisherId}/books")]

        public IEnumerable<Book> GetByPublisher(int authodId)

        {

            return GetBooksByPublisher(authorId);

        }

    }

ASP.NET Web API 2 lets both routing techniques be used in concert, with a single configuration change necessary. Here's another example:

    public static class WebApiConfig

    {

        public static void Register(HttpConfiguration config)

        {

            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(

                name: "Customer",

                routeTemplate: "api/{customer}/{id}",

                defaults: new { id = RouteParameter.Optional }

            );

        }

    }

Attribute routing exposes a lot of the power of the routing system, closer to where developers will work (controllers, actions), increasing the readability and understanding available in the controllers themselves.

However, this method of routing has its own syntax and complexities, so let's explore in more some detail.

Optional Parameters

To specify an optional parameter in a route attribute, append a '?' to the parameter declaration and define a default:

        [Route("books/{page?}")]

        public IEnumerable<Book> GetAll(int page = 0)

Alternatively, the default value can be included in the route definition:
 

      [Route("books/{page=0}")]

      public IEnumerable<Book> GetAll(int page)

A value must be declared for routing to occur correctly.

Constraints

Constraints are declared similarly in the route definition:

        [Route("author/{id:int}")]

        public Author Get(int id){

            return new Author(id);

        }

        [Route("author/{name}")]

        public Author Get(string name)

        {

            return new Author(name);

        }

The above lets an author be found by an integer, ID, or name.

Microsoft has detailed available built-in constraints, which are detailed in Table 1:

Constraint Key Description Example
Table 1: Built-in constraints in Web API
bool Matches a Boolean value {x:bool}
datetime Matches a DateTime value {x:datetime}
decimal Matches a Decimal value {x:decimal}
double Matches a 64-bit floating point value {x:double}
float Matches a 32-bit floating point value {x:float}
guid Matches a GUID value {x:guid}
int Matches a 32-bit integer value {x:int}
long Matches a 64-bit integer value {x:long}
minlength Matches a string with the specified minimum length {x:minlength(4)}
maxlength Matches a string with the specified maximum length {x:maxlength(8)}
length Matches a string with the specified length or within the specified range of lengths {x:length(6)}, {x:length(4,8)}
min Matches an integer with the specified minimum value {x:min(100)}
max Matches an integer with the specified maximum value {x:max(200)}
range Matches an integer within the specified range of values {x:range(100,200)}
alpha Matches English uppercase or lowercase alphabet characters {x:alpha}
regex Matches the specified regular expression {x:regex(^\d{3}-\d{3}-\d{4}$)}

 

Route Prefixing

In a controller with simpler responsibilities, one can adorn a controller with a RoutePrefix attribute, which lets actions be either undecorated specifically or brought into more detail. Here's an example:

    [RoutePrefix("author")]

    public class AuthorController : ApiController

    {

        [Route("{id:int}")]

        public Author Get(int id){

            return new Author();

        }

        [Route("{name}")]

        public Author Get(string name)

        {

            return new Author();

        }

    }

Attribute routing, though much awaited, is a great step in the right direction. Traditionally, it was a good idea to have controllers as a part of Web API because the majority of users were MVC developers. This approach reduces the learning curve. However, once you start using Web API, you'll realize that there's no real need for a controller if you have experience in using Node.js, Restify, or even Nancy in .NET.

I think this limitation is easily overcome with attribute routing, and I surely would recommend using this feature wherever applicable.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish