Routing In RESTful APIs Using .NET Core

Introduction

When it comes to listing the best practices for REST APIs, the mechanism, Routing always makes its place on the top of the stack. Today, in this article, we will dirty our hands with 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 a 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 in the developer community, is an interesting feature in which to deep dive. Routing is a functionally 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 to 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 of type text decorated with parameters. Once the request is received, it tries to match requested URI with this defined route templates. The only merit of using this routing type is, templates are defined at a single location in application solution structures, leveraging the template rules religiously across the controllers and actions.

Then, why is Attribute routing important? Yes, it is not only important but strongly recommended for API 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 clubbed with their hierarchical data or child resources. For eg. Departments have Employees, Songs have singers, Movies have actors and so on. URIs expected in such scenarios are,

/movies/1/actors

In the case of multiple controllers and huge resources this type of URI is difficult though achievable using convention-based routing, but at the cost of scaling and performance. This hits the key consideration area of designing scalable APIs. Here is 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 Controller 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 use 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 the 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 routing. Defining route attribute to the action or the controller takes precedence over conventional routing. Let’s be more precise 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 template applies to each action.

Configuring

When you create a WEB API with .NET Core framework, you can notice in its Startup.cs file,

 

  1. void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {  
  2.     app.UseMvc();  
  3. }   

This declaration of, ‘app.UseMvc()’ at configure section, enables Attribute Routing. This are by-default configurations of .NET Core applications. Explicit configurations 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,

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using Microsoft.AspNetCore.Mvc;  
  6.    
  7. namespace WebAPIwiithCore.Controllers  
  8. {  
  9.     [Route("api/[controller]")]  
  10.     publicclassMoviesController : Controller  
  11.     {  
  12.         // GET api/values  
  13.         [HttpGet]  
  14.         publicIEnumerable<string> Get()  
  15.         {  
  16.             returnnewstring[] { "value1""value2" };  
  17.         }  
  18.       
  19.    
  20.         // GET api/values/5  
  21.         [HttpGet("{id}")]  
  22.         publicstring Get(int id)  
  23.         {  
  24.             return"value";  
  25.         }  
  26.    
  27.         // POST api/values  
  28.         [HttpPost]  
  29.         publicvoid Post([FromBody]string value)  
  30.         {  
  31.         }  
  32.    
  33.         // PUT api/values/5  
  34.         [HttpPut("{id}")]  
  35.         publicvoid Put(int id, [FromBody]string value)  
  36.         {  
  37.         }  
  38.    
  39.         // DELETE api/values/5  
  40.         [HttpDelete("{id}")]  
  41.         publicvoid Delete(int id)  
  42.         {  
  43.         }  
  44.     }  
  45. }  

If you notice carefully, controller class ‘ValuesController’, is decorated by route,

[Route("api/[controller]")]

The feature is introduced in .NET Core framework known as Route token. The token [controller] replaces 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,

  1. [Route("api/[controller]")]  
  2. publicclassValuesController : Controller{ ... }// Matches '/api/Values’  

Now let’s change controller name to MoviesController;

  1. [Route("api/[controller]")]  
  2. publicclassMoviesController : Controller{ ... }// Now Matches '/api/Movies’  

Here, URI ‘api/values’ are working perfectly  earlier, now it will throw an error (404 not found). Changing URI with ‘api/Movies’ will provide the desired response.

The same applies for [action] and [area] with their respective action methods and areas.

Now, let’s consider the below example,

  1. [Route("api/[controller]")]  
  2. publicclassMoviesController: Controller {  
  3.     [HttpGet]  
  4.     publicIEnumerable < string > Get() {  
  5.             returnnewstring[] {  
  6.                 "value1",  
  7.                 "value2"  
  8.             };  
  9.         }  
  10.         [HttpPost]  
  11.     publicvoid Post([FromBody] string value) {  
  12.         return;  
  13.     }  
  14. }  

For the above given code snippet,

URI,

  1. // Get /api/Movies/   will match Method 1.
  1. // Post /api/Movies/ will match Method 2.

Notice, both the URIs are the same, the only difference lies in how it’s been called, by Get or Post method. This makes Web API routing differ from MVC routing.

Patterns

Let’s look into a few other patterns with examples, 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.

  1. [Route("api/[controller]")]  
  2. publicclassMoviesController: Controller {  
  3.     [HttpGet("v1")]  
  4.     publicIEnumerable < string > Get() {  
  5.             returnnewstring[] {  
  6.                 "V1.value1",  
  7.                 "V1.value2"  
  8.             };  
  9.         }  
  10.         [HttpGet("v2")]  
  11.     publicIEnumerable < string > Get() {  
  12.         returnnewstring[] {  
  13.             "V2.value1",  
  14.             "V2.value2"  
  15.         };  
  16.     }  
  17. }  

Note, versioning is mostly taken care of 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.

  1. [Route("api/[controller]")]  
  2. publicclassMoviesController: Controller {  
  3.     [HttpGet("{id}")]  
  4.     publicIEnumerable < string > Get(int id) {  
  5.             returnnewstring[] {  
  6.                 "V2.value1",  
  7.                 "V2.value2"  
  8.             };  
  9.         }  
  10.         [HttpGet("notedited")]  
  11.     publicIEnumerable < string > Get() {  
  12.         returnCollections..  
  13.     }  
  14. }  

URI,

  1. // Get /api/Movies/123  will match Method 1.
  1. // Get /api/Movies/notedited will match Method 2.

Multiple Parameters Types

In this example, "id" is a parameter that could be passed as the number or as an alphabet, any free string.

  1. [Route("api/[controller]")]  
  2. publicclassMoviesController: Controller {  
  3.     [HttpGet("{id:int}")]  
  4.     publicIEnumerable < string > Get(int id) {  
  5.             returnnewstring[] {  
  6.                 "V2.value1",  
  7.                 "V2.value2"  
  8.             };  
  9.         }  
  10.         [HttpGet("id:aplha")]  
  11.     publicIEnumerable < string > Get(string id) {  
  12.         returnnewstring[] {  
  13.             "V2.value1",  
  14.             "V2.value2"  
  15.         };  
  16.     }  
  17. }  

URI,

  1. // Get /api/Movies/123  will match Method 1.
  1. // Get /api/Movies/abc will match Method 2.

Above example has something to be noticed, while with routing we have used,

HttpGet("{id:int}")]

Mentioning parameter data types to be accepted, are termed as Constraints. We will go through this in a later article.

Multiple Parameters

In this example, ‘id’and ‘authorid’ is a parameter that could be passed as number.

  1. [Route("api/[controller]")]  
  2. publicclassBooksController: Controller {  
  3.     [HttpGet("{id:int}/author/{authorid:int}")]  
  4.     publicIEnumerable < string > Getdetails(int id, intauthorid) {  
  5.         returnnewstring[] {  
  6.             "V2.value1",  
  7.             "V2.value2"  
  8.         };  
  9.     }  
  10. }  

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,

  1. [Route("api/[controller]")]  
  2. publicclassMoviesController: Controller {  
  3.     [HttpPut("Buy")]  
  4.     [HttpPost("Checkout")]  
  5.     publicMovieordermovie() {  
  6.         returnSome value…  
  7.     }  
  8. }  

As shown in the example, Method ‘Ordermovie’ returns some value with model class ‘Movie’. The method defines two routes. One with HTTP verb Put, used mostly for updating operations in CRUD, and the other with HTTP verb Post, used for creating or adding data. Both are referring to the 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 a 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,

  1. [Route("api/Store")]  
  2. [Route("api/[controller]")]  
  3. publicclassMoviesController: Controller {  
  4.     [HttpPut("Buy")]  
  5.     [HttpPost("Checkout")]  
  6.     publicMovieordermovie() {  
  7.         returnSome value…  
  8.     }  
  9. }  

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 

  1. [HttpGet("api/constraint/{id:int}")]  
  2. publicIEnumerable < string > Get(int id) {  
  3.     returnnewstring[] {  
  4.         "V2.value1",  
  5.         "V2.value2"  
  6.     };  
  7. }  

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 achieved 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 

  1. [HttpGet("api/constraint/{id:int:min(1)}")]  
  2. publicIEnumerable < string > Get(int id) {  
  3.     returnnewstring[] {  
  4.         "V2.value1",  
  5.         "V2.value2"  
  6.     };  
  7. }  

URIs

  1. api/constraint/1 or >1 will match.
  2. api/constraint/0 will not match.
  3. 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 the most recommended implementation in API designing stack. Right from the 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 threads of differences over using routing.

References

  1. https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing
  2. https://www.youtube.com/watch?v=e2qZvabmSvo