Sunday, December 8, 2013

Attribute Routing in ASP.NET Web API 2

In the previous post, we discussed about HTTP routing in ASP.NET Web API. With version 2, Microsoft has incorporated a more flexible version of routing into the API. This is called Attribute Routing. The feature is contributed to ASP.NET MVC project by Tim McCall []. Conventional routing supports defining the routes on global level. Since attribute routing allows defining routes on controllers and their actions, it provides extraordinary flexibility in defining these routes. The feature can be used to define resource hierarchies in the same controller.It also enables us to define options parameters, default values and resource and parameter constraints.

Enabling Attribute Routing
Let's first create an ASP.NET Web API project. Let's name this project as InstituteService.

Attribute Routing is included in ASP.NET Web API 2. Visual Studio 2013 supports creating ASP.NET Web API 2 based projects out of the box. We can also create then in Visual Studio 2012 with the help of Microsoft.AspNet.WebApi.WebHost nuget package.

The service is required to provide information about students and their courses. Here we are introducing types Student and Course to hold information about students and courses respectively. Each course maintains a list of students attending the course.

We need to update Web API configuration to use Attribute routing. The configuration is done in WebApiConfig. The new version of ASP.NET Web API has introduced a new version in HttpConfiguration type with an extension method MapHttpAttributeRoutes. We need to update the code to use this method instead.

Now let us add an empty controller for our Institute service. Here we are creating it as an empty API Controller.

As we have discussed in previous posts, a Web API controller inherits from ApiController type from System.Web.Http.

Now let us seed some data which could be returned from the service. In order to keep the example simple, let us create some data in the service constructor. Here we are creating three students assigned to two different courses. The first course is assigned with all the three students when the second course is just being attended by Muhammad and Roosevelt.

Defining Routes Using [RoutePrefix] & [Route] Attributes
ASP.NET Web API 2 support defining routes by introducing RoutePrefix and Route attributes. The RoutePrefix attribute can be used on controllers. This allows definition of a default prefix for all the actions in the controller.

Now we can add relative routes to individual actions. The routes are defined on actions using [Route] attribute. Here we are adding two actions to provide the list of course and students of the institute.

The above actions can be used to fulfill client request as follows:

Parameterized Routes
In the previous post we discussed how parameterized routes can be used for Web API resources. For conventional routing only certain parameters can be used as part of the request routes. For any additional parameters, we had to use query parameters. Since attribute routes are defined on individual actions, we can use action parameters for parameterized routes. In the following example, we are using courseId as route parameter. This is one of the parameter of the action defined below:

The above action can be used with the following request. As you can notice, the client is requesting all the students attending course (CourseId = 2). This course is only being attended by Muhammad and Roosevelt.

Route Constraints
Attributes routes also supports constraints. The list of constraints can be found on msdn (route constraints). In the following example we are defining two actions. The first actions is providing the list of courses being attended by a student identified by StudentId parameter. In this case, the constraint is defined to check that the Id must be an integer value. This is achieved using int contraint. For the second action, the same can be requested using student's name as parameter. Here we are using alpha constraint. This is to make sure that the name only include alpha characters. The same can be achieved using regex constraint (commented).

Let's see how we can request the above resources. Here the service is returning the list of courses attended by Muhammad. As you can notice, the returned data also includes the list of all students attending the courses. You might want to create a different type to return the course data to clients.

ASP.NET Web API 2 also supports defining custom constraints. It provides IHttpRouteConstraint interface in System.Web.Http.Routing namespace. We need to provide definition for Match method of the interface. The constraint is considered passed when the method returns true.

Default Values for Optional Route Parameters
Web API 2 also supports optional route parameters. They are identified with a question mark followed by the parameter name. The parameters are still identified with the brackets (e.g. {studentId?}). The default values can be specified as a method parameter as we generally specify for optional parameters for a method. In the following example, we are defining an action with studentId as a optional route parameter. The default value for the parameter is 1, so it would be returning all the courses attended by Muhammad.

The other option to define default values is by including the default value as part of the route. In order to get the same behavior as above, we can define the route as follows:

Although the behavior seems to be same for the above two examples, but they are a little different in the way they work out the value for the optional parameter in case one is not specified in the client request. The first example just causes the action to be used in case the parameter is not specified, and the value for the parameter is through optional parameter for C# method. For the second example, ASP.NET Web API framework is responsible for assigning value for the parameter. This is done through the model binding process. We can also define our custom model binder but lets just keep it for some other post.

Versioning Resources
ASP.NET Web API 2 also makes it super easy for versioning the API. As we have discussed before we might not want to expose the list of students who are signed up for a particular course when we return the course attended by a particular student. Here we are just introducing a new type for holding courses with the details including Id and its name.

We can use this type to introduce a new version for such requests. Here we have just added a literal in our resource route url. We can use the same for all actions exposing the same version of types to our clients.

We can use the above action using the same route prefix as we define on the Api controller. Here we are using the same Api by exposing a different version of Course type to clients.

Overriding RoutePrefix for Actions
We have been discussing how we can define routes for Web API actions. The specified route on actions is used with the route prefix defined on controller level [RoutePrefix attribute]. What if we want to override the route on some of these actions. ASP.NET Web API 2 supports this. This is defined using tilde (~) character. In the following example, we are doing the same thing:

Now we don't need to use the route prefix defined on the controller. We can access the resource provided by the above as follows:

Route Order and Precendence
Since the routes are defined on the actions themselves, it is natural to wonder what would happen if there are multiple actions qualify for the same URL. This is very common when optional route parameters are used. In the following example we have two actions with actions annotated with independent routes overriding route prefixes.

Now which of these action would be used when we use the URL below. It seems that the first action is being used. But why?

As a matter of fact, there is a particular logic for route precedence for these actions. There are certain ASP.NET Web API rules in action. And these rules are being applied here causing the first action to be used for the request. So now we know why this is happening whatever is happening. But we are interested in knowing what these rules are. The following is copied from msdn.

So in the case of our example, since the first action is annotated with literal route, it is taking precedence on the route defined on the other action with parameterized route. As is apparent from the above rules, we can override this order by defining an explicit RouteOrder on the actions.

Now if we use the same URL, we are definitely hitting the second action as follows:



Susan Watson said...

Hi Muhammad-
Somebody has messed with your file. When I tried to download I get " is malicious, and Chrome has blocked it.

Thanks for the notes, though.

Muhammad Shujaat Siddiqi said...

Hi Susan, Can you try using some other browser?