no longer exists, and it is thankfully no longer needed because the .NET Web API is now 1000x better.

I have a lot of problems with the default .NET Web API that Microsoft pushes for people to use. You can only easily have two GETs, and one PUT, POST and DELETE route per controller. There is no easy way to create more complicated nested routes, i.e. GET /v1/person/:id/contact/:id, or routes that deviate from the norm. The routing syntax (not just specific for the Web API but other .NET services too) is horrible and unwieldy, and all routes must be defined in a separate file. Route constraints, precedence and validation are also tricky.

All of this may turn you off of writing an API in .NET, but there is an easy solution to all of these problems in the form of AttributeRouting.NET. This NuGet package allows you to define routes directly on controller methods, with out of the box support for route constraints, prefixes, precedence etc. It is compatible with the Web API and also .NET MVC.

To get started, just run the following NuGet install command:

Install-Package AttributeRouting

AttributeRouting will set some stuff up in your project for you as well, and will detect whether your project is written in C# or VB.NET.

Defining Routes

Route definition is easy, because all routes are defined on controller methods. For example, here is a Person controller with a route prefix of v1/ and multiple routes defined.

using AttributeRouting.Web.Mvc;
using Newtonsoft.Json;

public class PersonController
  public ActionResult GetAll() {
    var people = db.person.All(); // These orm queries are pseudocode, your own implementation will dictate.
    return Content(JsonConvert.SerializeObject(people), "application/json");

  public ActionResult Get(int id) {
    var person = db.person.Find(id);
    return Content(JsonConvert.SerializeObject(person), "application/json");

  public ActionResult Get(int id) {
    var contact = db.person.Find(id).contact;
    return Content(JsonConvert.SerializeObject(contact), "application/json");

  public void Update(int id, Person p) {
    var person = db.person.Update(p);
    Response.StatusCode = 204;

  public int Add(Person p) {
    var person = db.person.Add(p);
    Response.StatusCode = 201;

  public void Delete(int id) {
    Response.StatusCode = 204;

As you can see, these routes define the usual CRUD actions that you would have on a REST API resource along with another GET route that gets a contact record for the person. There are several handy route declaration Attributes that AttributeRouting makes use of.

  1. RoutePrefix – A route prefix is appended to all routes in the controller. In this case, I’ve just used the API version in the route but you could use them for whatever you want really.

  2. Route Constraints – For each route that involves an ID, I’ve appended :int to the parameter in the route definition. There are heaps of default built in constraints.

  3. Route Definitions – The best part of AttributeRouting, routes are defined on the method. So any GET, PUT, POST or DELETE routes are declared as an attribute with that keyword, with the parameters required.

There are a couple of things to note when defining routes and combining with the default .NET model binding. First of all, querystring parameters do not need to be defined in the route. So for the route /person?search={name}, the route definition would just be [GET("person")] with the method public ActionResult GetPerson(string search = "") { }.

The second thing to keep in mind is to be careful with the default .NET model binder sometimes being NULL on POST.


Finally, if weird stuff is happening or if you are just curious which routes are being generated by AttributeRouting, then you can visit routes.axd in your application, and a list of all routes will be shown.


Hopefully, AttributeRouting helps you out a lot on your next project. I’ve been using to for both the MVC APIs I’ve been working on AND the client-side MVC applications just for defining normal controller routes. It’s an incredibly useful and robust extension, and it feels like what Web API should be on its own.