Introduction
When it comes to listing about best practices for REST APIs, the mechanism, Routing always makes its place on top of the stack. Today in this article will dirty our hands-on Routing concepts with REST (web) APIs, specific to .NET Core.
For novice APIs developers, technical consultants, and all other IT professionals associated with REST APIs, especially with Microsoft technologies stack, this article will explain the importance and capabilities of Routing focusing Attribute Routing in REST APIs with Microsoft’s .NET Core.
Overview
Routing, an open debatable topic among many developer community, makes it an interesting feature to deep dive in. Routing is functional based tag or Uri template used by APIs to match the desired action or methods expected to be executed. There are two types or rather two different types of Routing being used during development. Namely, ‘Convention-based Routing’ the elder son in the REST routing family followed by ‘Attribute Routing’ the most lovable son till date. As mentioned earlier it’s an open debatable topic over using which type of Routing mechanics during APIs development and designing phase as well.
In ‘Convention-based Routing’, route templates are defined by developers as per requirement, basically a set of strings type text decorated with parameters. Once request is received, it try to match requested URI with this defined route templates. The only merit of using this routing type is, templates are defined at single location in application solution structure, leveraging the template rules religiously across the controllers and actions.
Then why Attribute routing is important? Yes, it is not only important but strongly recommended for APIs development by developers and architects across the communities. Though convention-based routing has its own Pros, while building a good API, there are few considerations, where this type of routing is not advisable. There are common URI patterns in REST APIs, which are tough to support by convention-based routing. Consider, a set of Response data or resources, are often club with its hierarchical data or child resources. For eg. Department have Employees, Songs have singers, Movie have actors and so on. URIs expected in such scenarios are like,
/movies/1/actors
In case of multiple controllers and huge such resources this type of URI is difficult though achievable using convention-based routing, but at cost of scaling and performance. This hits the key consideration area of designing scalable APIs. Here where another routing type, Attribute Routing, plays a role.
Attribute Routing
What is Attribute Routing?
Technically, Attribute routing is all about attaching a route, as an attribute, to a specific controller or action method. Decorating Controllers and its method with [Route] attribute to define routes is called Attribute Routing. In simpler terms, using [Route] attribute with controllers and method is Attribute Routing.
[Route (“api/customers/{id}/orders”)]
It started from Web API 2, and now is the most recommended and adapted Routing type in RESTful APIs design and development.
Why using Attribute Routing
As the name indicates, attribute routing uses attributes to define routes. Attribute routing gives you precise control over the URIs than convention-based routing in your APIs. Above described scenario of Hierarchical resources can be easily achieved by Attribute Routing, with making no compromise with scalability of APIs.
Also, versioning APIs, overloading URI segments and multiple parameter type patterns can be achieved through attribute routing with ease.
Working with Attribute Routing
Any route attribute on the controller makes all actions in the controller attribute routed. Defining route attribute to the action or the controller, takes precedent over conventional routing. Let’s be more précised to .NET Core APIs, it comes by default with Attribute routing. Attribute routing requires detailed input to specify a route. However, it allows more control of which route templates applies to each action.
Configuring
When you create a WEB API with .NET Core framework, you can notice in its Startup.cs file,
void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{ app.UseMvc();}
This declaration of, ‘app.UseMvc()’ at configure section, enables Attribute Routing. This are by-default configuration of .NET Core applications. Explicit configuration, thus are not required for enabling Attribute routing for .NET Core Web APIs.
Below namespace is used for decorating [Route] as an attribute.
using Microsoft.AspNetCore.Mvc;
The core difference with Web API over MVC routing is that it uses the HTTP method, not the URI path, to select the action.Attribute routing also uses HTTP Action verbs to define action against methods under controller, as requested.
[HttpGet], [HttpPost], HttpPut(“{id}”)], [HttpDelete(“{id}”)] and all other documented action verbs.
In .NET Core projects, by default, Controllers are decorated with CRUD methods specifying respective HTTP action verbs.
Below is the default controller being created by .NET Core,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace WebAPIwiithCore.Controllers
{
[Route(“api/[controller]”)]
publicclassMoviesController : Controller
{
// GET api/values
[HttpGet]
publicIEnumerable<string> Get()
{
returnnewstring[] { “value1”, “value2” };
}
// GET api/values/5
[HttpGet(“{id}”)]
publicstring Get(int id)
{
return”value”;
}
// POST api/values
[HttpPost]
publicvoid Post([FromBody]string value)
{
}
// PUT api/values/5
[HttpPut(“{id}”)]
publicvoid Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete(“{id}”)]
publicvoid Delete(int id)
{
}
}
}
If you notice carefully, controller class ‘ValuesController’, is decorated by route,
[Route(“api/[controller]”)]
The feature is introduce in .NET Core framework known as Route token. The token [controller] replace the values of the controller name from the action or class where the route is defined.
Here controller name is Values decorated by Route Controller token,
[Route(“api/[controller]”)]
publicclassValuesController : Controller{ … }// Matches ‘/api/Values’
Now let’s change controller name to MoviesController;
[Route(“api/[controller]”)]
publicclassMoviesController : Controller{ … }// Now Matches ‘/api/Movies’
Here, URI ‘api/values’perfectly working earlier, now would throw an error (404 not found). Changing URI with ‘api/Movies’ will provide desired response.
The same applies for [action] and [area] with their respective action methods and areas.
Now let’s consider, below example,
[Route(“api/[controller]”)]
publicclassMoviesController:Controller
{
[HttpGet]
publicIEnumerable<string> Get()
{returnnewstring[] { “value1”, “value2” };}
[HttpPost]
publicvoid Post([FromBody]string value)
{return;}
}
For above given code snippet,
URI,
- // Get /api/Movies/ will match Method 1.
- // Post /api/Movies/ will match Method 2.
Notice, both the URI, are same, the only difference lies in how it’s been called, by Get or Post method. This make Web API routing differ from MVC routing.
Patterns
Let’s look into few other patterns with example, as mentioned above, eased by attribute routing.
API versioning
In this example, “Get /api/movies/v1/” would be routed to a Method 1and “Get /api/movies/v2/” would get routed to Method 2.
[Route(“api/[controller]”)]
publicclassMoviesController: Controller
{
[HttpGet(“v1”)]
publicIEnumerable<string>Get()
{returnnewstring[] { “V1.value1”, “V1.value2” };}
[HttpGet(“v2”)]
publicIEnumerable<string> Get()
{returnnewstring[] { “V2.value1”, “V2.value2” };}
}
Note, versioning are mostly taken care with different Controllers. Here for easy understanding, we have preferred to depict it with different Methods with same signatures.
Example clearly shows, how attribute routing is the simplest way to deal with complex situations.
Overloaded URI
In this example, “id” is a parameter that could be passed as number, but “notedited” maps to a collection.
[Route(“api/[controller]”)]
publicclassMoviesController: Controller
{
[HttpGet(“{id}”)]
publicIEnumerable<string> Get(int id)
{returnnewstring[] { “V2.value1”, “V2.value2” };}
[HttpGet(“notedited”)]
publicIEnumerable<string> Get()
{returnCollections..}
}
URI,
- // Get /api/Movies/123 will match Method 1.
- // Get /api/Movies/notedited will match Method 2.
Multiple Parameters Types
In this example, “id” is a parameter that could be passed as number or as an alphabet, any free string.
[Route(“api/[controller]”)]
publicclassMoviesController: Controller
{
[HttpGet(“{id:int}”)]
publicIEnumerable<string> Get(int id)
{returnnewstring[] { “V2.value1”, “V2.value2” };}
[HttpGet(“id:aplha”)]
publicIEnumerable<string> Get(string id)
{returnnewstring[] { “V2.value1”, “V2.value2” };}
}
URI,
- // Get /api/Movies/123 will match Method 1.
- // Get /api/Movies/abc will match Method 2.
Above example has something to be noticed, while routing we have used,
HttpGet(“{id:int}”)]
Mentioning parameter data types to be accepted, are termed as Constraints. Will go through about this later in article.
Multiple Parameters
In this example, ‘id’and ‘authorid’ is a parameter that could be passed as number.
[Route(“api/[controller]”)]
publicclassBooksController: Controller
{
[HttpGet(“{id:int}/author/{authorid:int}”)]
publicIEnumerable<string> Getdetails(int id, intauthorid)
{returnnewstring[] { “V2.value1”, “V2.value2” };}
}
Matching URI: // Get api/books/1/author/5where 1 matches to ‘id’ and ‘5’ to authorid in the given method.
Multiple Routes
Attribute routing allows us to define multiple routes for same controller and action or method. Let’s understand it with example,
[Route(“api/[controller]”)]
publicclassMoviesController: Controller
{
[HttpPut(“Buy”)]
[HttpPost(“Checkout”)]
publicMovieordermovie()
{returnSome value…}
}
As shown in example, Method ‘Ordermovie’ returns some value with model class ‘Movie’. The method defines two routes. One with HTTP verb Put, used mostly for update operation in CRUD, and other with HTTP verb Post, used for creating or adding data. Both are referring to same method.
In this case, below URIs would be matching the routes,
URI 1 matches // PUT api/movies/buy
URI 2 matches // Post api/movies/checkout
Note: Route 1 & Route 2 are used for better understanding purpose and has nothing to do with its ordering.
We can even define multiple Routes on Controller. In this case both routes from controller combines with both routes of action. See below example,
[Route(“api/Store”)]
[Route(“api/[controller]”)]
publicclassMoviesController: Controller
{
[HttpPut(“Buy”)]
[HttpPost(“Checkout”)]
publicMovieordermovie()
{returnSome value…}
}
In this case, below URIs would be matching the routes,
URI 1 matches // PUT api/movies/buy& api/store/buy
URI 2 matches // Post api/movies/checkout& api/store/checkout
Route Constraint
Route constraint provides control over matching parameters used in route. Syntax for defining parameters in route is “{parameter:constraint}”.
Example:
[HttpGet(“api/constraint/{id:int}”)]
publicIEnumerable<string> Get(int id)
{returnnewstring[] { “V2.value1”, “V2.value2” };}
Matching Routes: api/constraint/1
Only integer value will be routed to ’Get’ method responding with valid resource. Any other non-integer value will not be entertained by method, like, api/constraint/abc will not be routed.
Here, there could be an issue. If client calls, api/constraint/0 still this would be routed to Get method, which is wrong. So to curb this issue, we can add another constraint to parameter for accepting value greater than zero. This could be achieve by adding constraint ‘min(1)’ where 1 is argument accepted by ‘min’ constraint. Arguments can be accepted in parentheses ‘( )’
We can add multiple constraints on single parameter by separating constraints by colon.
Syntax: “{parameter:constraint:constraint}”.
Example:
[HttpGet(“api/constraint/{id:int:min(1)}”)]
publicIEnumerable<string> Get(int id)
{returnnewstring[] { “V2.value1”, “V2.value2” };}
URIs:
- api/constraint/1 or >1 will match.
- api/constraint/0 will not match.
- api/constraint/-1 or negative number will not match.
Similarly constraints like ‘alpha’ for string, ‘bool’ for Booleanvalue, ‘datetime’ for DateTime value, ‘min’, ‘max’ , ‘range’ for values in specific range and few others can be used in route as constraint.
Attribute routing though a feature, stands to be most recommended implementation in API designing stack. Right from scalability of APIs to making APIs readable to client, attribute routing plays an important role in APIs development. Microsoft enabling default routing as attribute routing in its .Net Core framework, closed all the possible thread of differences over using routing.
References
- https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing
- https://www.youtube.com/watch?v=e2qZvabmSvo
Author Bio: This tutorial is shared by asp.net web development company to explain the work of routing in RESTful APIs using .NET Core. If you face any technical difficulties in implementing this tutorial, let us know, we would be happy to help. Happy coding.