How to use Attribute Routing using ASP.NET Web API 2?
The goal of this post is to explain how to use Attribute routing using ASP.NET Web API 2. The code that I’ll use in this post is from my another post that explains how to create CRUD operations using ASP.NET Web API 2.
So, the `ProductsController` class that I created in the linked post above is shown below:
namespace Prodo.API.Controllers
{
[RoutePrefix("api/Products")]
public class ProductsController : ApiController
{
private static readonly ProductRepository ProductRepository = new ProductRepository();
[Route("~/api/Categories/{categoryId:int}/Products")]
public IHttpActionResult Get(int categoryId)
{
return Ok(ProductRepository.GetProducts(categoryId));
}
//[Route("{id:int?}")] optional param
//[Route("{id:int=4}")] default param value
[Route("{id:int}")]
public IHttpActionResult GetProduct(int id)
{
var product = ProductRepository.GetProduct(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
[Route("")]
public HttpResponseMessage Post([FromBody]Product product)
{
if (product == null) throw new ArgumentNullException("product");
var savedProduct = ProductRepository.SaveProduct(product);
var response = Request.CreateResponse(HttpStatusCode.Created, savedProduct);
var uri = Url.Link("DefaultApi", new { id = product.Id, controller = "Products" });
response.Headers.Location = new Uri(uri);
return response;
}
[Route("{id:int}")]
public void Put(int id, [FromBody]Product product)
{
if (product == null) throw new ArgumentNullException("product");
ProductRepository.UpdateProduct(id, product);
}
[Route("{id:int}")]
public void Delete(int id)
{
var product = ProductRepository.GetProduct(id);
if (product == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
ProductRepository.Delete(id);
}
}
}
Why Attribute Routing?
Resources often contain child resources: Categories have products, Customers have orders, movies have actors, books have authors, and so forth. It’s natural to create URIs that reflect these relations: `/categories/1/products`.
This type of URI is difficult to create using convention-based routing (ASP.NET Web API V1). Although it can be done, the results don’t scale well if you have many controllers or resource types.
If Attribute Routing doesn’t work
If you have used attribute routing like above and still your application is not responding to the expected routes, then please refer my other post that should fix a common known issue when working with attribute routing.
Route Prefix Attribute
This attribute annotates a controller with a route prefix that applies to all actions within the controller. For `ProductsController`, the prefix api/Products is used which applies to all actions within the controller.
Route Attribute
Use this attribute to place on an action to expose it directly via a route.
The route to consume `Get` method of `ProductsController` is interesting. As such the route starts with `~`, it overrides the route prefix. Next, notice that a route template can have a parameter in it using `{}`, for example: `[Route(“~/api/Categories/{categoryId:int}/Products”)]`.
Parameter(s) & Constraints in Route
[Route("~/api/Categories/{categoryId:int}/Products")] instructs that `categoryId` is a parameter and `:int` instructs that this parameter is of type integer which is a constraint. So, the parameter’s integer value is then passed to the `categoryId` parameter that is of type integer of `Get` method.
There are many other constraints available in Web API V2 out of the box. However, if you would like to create your custom constraint then that is certainly possible using `IHttpRouteConstraint` interface.
Optional URI Parameters and Default Values
Few other examples of specifying parameters in a route where a parameter – can be optional, can have a default value, can just has a type information and when the route template is empty, this means just use the route prefix along with a matching HTTP verb action as shown below:
[Route("{id:int?}")] // an optional param
[Route("{id:int=4}")] // a default param value
[Route("{id:int}")] // type of parameter
[Route("")] // empty route template; instructs to use route prefix along with a matching HTTP verb.
Route Names
In Web API, every route has a name. Route names are useful for generating links, so that you can include a link in an HTTP response.
[Route("api/books/{id}", Name="GetBookById")]
Route Order
When the ASP.NET Web API V2 framework tries to match a URI with a route, it evaluates the routes in a particular order. To specify the order, set the RouteOrder property on the route attribute. Lower values are evaluated first. The default order value is zero.
Here is how the total ordering is determined:
- Compare the RouteOrder property of the route attribute.
- Look at each URI segment in the route template. For each segment, order as follows:
- Literal segments.
- Route parameters with constraints.
- Route parameters without constraints.
- Wildcard parameter segments with constraints.
- Wildcard parameter segments without constraints.
- In the case of a tie, routes are ordered by a case-insensitive ordinal string comparison (OrdinalIgnoreCase) of the route template.
Let’s understand with the help of an example:
[RoutePrefix("orders")]
public class OrdersController : ApiController
{
[Route("{id:int}")] // constrained parameter
public HttpResponseMessage Get(int id) { ... }
[Route("details")] // literal
public HttpResponseMessage GetDetails() { ... }
[Route("pending", RouteOrder = 1)]
public HttpResponseMessage GetPending() { ... }
[Route("{customerName}")] // unconstrained parameter
public HttpResponseMessage GetByCustomer(string customerName) { ... }
[Route("{*date:datetime}")] // wildcard
public HttpResponseMessage Get(DateTime date) { ... }
}
These routes are ordered as follows.
- orders/details
- orders/{id}
- orders/{customerName}
- orders/{*date}
- orders/pending
This sums up my post to explain how to use Attribute Routing using ASP.NET Web API V2. If you would like to learn more about this framework, please read my other ASP.NET Web API V2 posts.
