Attribute based routing in ASP.NET Web API

Β· 1111 words Β· 6 minutes to read

Routing is the key aspect of every MVC application - after all, it’s how people get to your application, and how search engines see it. As flexible as routing in ASP.NET MVC has been, one would often end up in frustrating situations where more flexibility was needed (or you simply started getting lost in a maze of routes). One of the finest extensions to ASP.NET MVC I have ever worked with is the excellent library AttributeRouting by Tim McCall.

Last month, through the great work of Kamran Ayub, the library has been extended to support ASP.NET Web API, and is now available on NuGet. Let’s have a look at how it can immediately make your life easier and drastically improve the way you handle your routes.

Getting the appropriate stuff from NuGet πŸ”—

You need to start off with a Web API project, and grab the AttributeRouting for Web API from NuGet. The package name is AttributeRouting.WebAPI and it is dependent on the WebActivator.

Note: If you want to use AttributeRouting for self-hosted Web API, grab the AttributeRouting.WebAPI.Hosted package instead.

What just happened πŸ”—

You’ll notice that upon installation from NuGet, a new folder appeared in your app, called App_Start, which contains a static class, AttributeRoutingHttp. This is how, through WebActivator AttributeRouting plugs itself automatically into the application start pipeline. If you don’t like this setup (like me) you can move the code to Global.asax.

public static class AttributeRoutingHttp  
{  
public static void RegisterRoutes(RouteCollection routes)  
{  
// ASP.NET Web API  
routes.MapHttpAttributeRoutes();  
}  
public static void Start()  
{  
RegisterRoutes(RouteTable.Routes);  
}  
}  

In here, your controllers are scanned for the routing attributes, and the plumbing happens - the routes are generated based on the attributes and registered in your application.

Using the attributes πŸ”—

The concept is wonderfully simple, just decorate your controllers and their actions with routing attributes, defining all kinds of route-specific information:

  • route names
  • access method/verb
  • route constraints
  • default and optional parameters
  • route areas
  • area mapping
  • route prefixes
  • route translations
  • routing conventions

and many more.

Since you do all that at such a low level (action or controller), you get the ultimate, granular control of routing in your application.

To be able to get started, first import the appropriate namespaces to your controllers - for ASP.NET Web API these are:

using AttributeRouting;  
using AttributeRouting.Web.Http;  

Let’s go through a few examples. For the record, I’ll be using the same simple repository that I used in this blog post.

Example - Basic HTTP methods πŸ”—

Let’s add a controller and a simple get all action.

public class UrlController : ApiController  
{  
static readonly IUrlRepository _repo = new UrlRepository();  
[GET("links")]  
public IEnumerable<Url> Get()  
{  
return _repo.GetAll();  
}  
}  

Now this can be accessed easily:

  • /links/ (via GET)

We can add more routes to same action, we can also mix the verbs

[GET("links")]  
[GET("urls")]  
[POST("postedlinks")]  
public IEnumerable<Url> Get()  
{  
return _repo.GetAll();  
}  

This action can now be invoked:

  • /links/ (via GET)
  • /urls/ (via GET)
  • /postedlinks/ (via POST)

We can easily pass paramters:

[GET("url/{id}")]  
public Url Get(int id)  
{  
return _repo.Get(id);  
}  

The single item can now be requested like this:

  • /url/1 (via GET)

Example - route constraints πŸ”—

We can also very easily add route constraints. In fact, through a terrific work of Xavier Poinas, there is a special syntax for that.

Let’s take the example from previous paragraph, and limit a range in which the ID can be used.

[GET("url/{id:range(1, 3)}")]  
public Url Get(int id)  
{  
return _repo.Get(id);  
}  

This will now respond to the following example:

  • /url/1 (via GET)
    but not to
  • /url/4 (via GET)

Routing constraints can , among there many terrific options, also take in regular expressions

\[GET(@"text/{e:regex(^[A-Z\]\[a-z\][0-9]$)}")]  
public string Get(string e)  
{  
return e;  
}  

This is a simple example, with the pattern [large letter] [small letter] [number]. So this action will respond to requests which follow such format only, i.e.:

  • /text/Ab1 (via GET)

As mentioned, there are many more constraints possible. All are listed here.

Example - default and optional parameters πŸ”—

You can define the default and optional paramteres for the route as well. Let’s use the previous example.

[GET("url/{id:range(1, 3)}")]  
[RouteDefault("id", "1")]  
public Url Get(int id)  
{  
return _repo.Get(id);  
}  

Now, if you request

  • /url/ (via GET)
    you’d actually get the same content as in
  • /url/1 (via GET)
    as the route parameter id would default to 1.

You can also set up optional parameters:

[GET("optionaltext/{?text}/{?text2}")]  
public string GetText(string text, string text2)  
{  
return text + " | " + text2;  
}  

This can be requested:

  • /optionaltext/ (via GET)
  • /optionaltext/hi (via GET)
  • /optionaltext/hi/hello (via GET)

Example – route prefixing πŸ”—

Another great functionality of AttributeRouting is the ability to prefix all the routes withing controller with a common prefix.

[RoutePrefix("items")]  
public class UrlController : ApiController  
{  
static readonly IUrlRepository _repo = new UrlRepository();

[GET("")]  
[GET("links")]  
public IEnumerable<Url> Get()  
{  
return _repo.GetAll();  
}  
}  

In this case the entire controller is prefixed by items. This means we access the above action like this:

  • /items/ (via GET)
  • /items/links/ (via GET)

Example - easy translations πŸ”—

AttributeRouting provides a really easy way for supporting transations. Just decorate your action methods with appropriate key, and in the Application_Start read the key values for a given language. Then initialize the routes by passing the translations in the config.

That will automatically generate translated routes for you. (Note, the example has translations hardcoded, normally you’d read them from resx or XML or DB).

Dictionary<string, string> items = new Dictionary<string, string>  
{  
{ "de", "german-items" },  
{ "fr", "french-items" }  
};

Dictionary<string, string> links = new Dictionary<string, string>  
{  
{ "de", "german-links" },  
{ "fr", "french-links" }  
};

var translations = new FluentTranslationProvider();

translations.AddTranslations().ForKey("itemsKey", items).ForKey("linksKey", links);

routes.MapHttpAttributeRoutes(conf =>  
{  
conf.AddRoutesFromController<aspnetwebapi\_attribute\_routing.Controllers.UrlController>();  
conf.AddTranslationProvider(translations);  
});

Please note that you need to specify to which Controller the translations need to be applied.

And next, the attributes declaration:

[RoutePrefix("items", TranslationKey = "itemsKey")]  
public class UrlController : ApiController  
{  
static readonly IUrlRepository _repo = new UrlRepository();

[GET("")]  
[GET("links", TranslationKey = "linksKey")]  
public IEnumerable<Url> Get()  
{  
return _repo.GetAll();  
}

}  

You can now access both the default, german and french routes:

  • /items/ (via GET)
  • /german-items/ (via GET)
  • /french-items/ (via GET)
  • /items/links/ (via GET)
  • /german-items/german-links/ (via GET)
  • /french-items/french-links/ (via GET)

Summary πŸ”—

These were just a handful of examples, but hopefully it was enough to draw you interest to the wonderful AttributeRouting library.

I have to admit, I’ve been using it for MVC for quite a while and I think it’s one of the best thing that could have happened for MVC developers. Since now it is available for ASP.NET Web API I guarantee you that after using it for a while you’ll never want to go back to the dark ages of declaring routes in a “traditional” way πŸ™‚

Cheers!

About


Hi! I'm Filip W., a software architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP