2 WEB API Routing and Attribute Routing
2 WEB API Routing and Attribute Routing
However, the following URI does not match, because it lacks the “api” segment:
/products/1
Note:
The reason for using “api” in the route is to avoid collisions between the Web API
and MVC routing. So, you can have “/products” go to the MVC controller, and
“/api/products” go to the Web API controller. Of course, if you don’t like this
convention, you can change the default route table that also we will discuss.
Once a matching route is found in the Route table. The Web API Framework then
selects the controller and the action. To find the controller, the Web API Framework
adds “Controller” to the value of the {controller} variable. To find the action, the
Web API Framework looks at the HTTP method and then looks for an action method
whose name begins with that HTTP method name.
For example, with a GET request, the Web API Framework looks for an action that
should start with “Get“, such as “GetProduct” or “GetAllProducts”. This
convention only applies to GET, POST, PUT, and DELETE methods. You can enable
other HTTP methods by using attributes on your controller that we will discuss in our
upcoming article.
Other placeholder variables in the route template, such as {id}, are mapped to
action method parameters.
Let us see an example for a better understanding. Suppose you define the following
Student controller
Here are some possible HTTP requests, along with the action that gets
invoked for each request.
Notice that the {id} segment of the URI, if present, is mapped to the id parameter
of the action. In our example, the Student controller defines two GET methods, one
with an id parameter and one with no parameters. It also defines one PUT method
which takes one parameter of student type from the request body.
Here another point you need to understand is that the POST request will fail as the
controller does not have any “Post” method.
In the above example, the FindStudentById action method is mapped to both GET
and HEAD HTTP Request.
Routing Variation in Web API by Action Name
With the default routing template, the Web API Framework uses the HTTP method to
select the action. However, if you want you can also create your own route where
the action name is included as part of the URI as shown in the below image.
In the above route template, the {action} parameter names the action method on
the controller. With this style of routing, you need to use the attributes to specify
the allowed HTTP methods.
Let us understand this with an example. Please consider the following
controller.
In the above example, a GET request for “api/Student/FindAllStudents” would
map to the FindAllStudents action method.
In ASP.NET Web API by using the ActionName attribute you can also override the
action method name. In the below Student Controller class, we have two actions
which map to “api/Student/Image“. One action method supports the GET request
while the other one supports the POST HTTP request.
When we create a route, it is also possible to provide default values for some or all
of the placeholders as shown in the below image.
We can also provide some constraints which will restrict how a URI segment can
match a placeholder as shown below.
The WEB API Framework tries to match the segments in the URI path with the route
template present in the Route table. The Literals in the template must match
exactly. A placeholder matches any value unless we specify some constraints. The
WEB API framework does not match other parts of the URI such as the hostname or
the query parameters. The framework always selects the first route in the routing
table that matches the URI.
There are two special placeholders used in WEB API such as “{controller}” and
“{action}”.
1. The “{controller}” placeholder provides the name of the controller.
2. Similarly, the “{action}” placeholder provides the name of the action. In Web
API, the usual convention is to omit the “{action}” placeholder. That’s why
when you create a new WEB API application, and then you can see that the
default route template created by the framework does not include the action
placeholder.
Defaults
If you provide a default value for a placeholder, then the route will match a URI that
is missing those segments. For example:
The URI “http://localhost/api/student/public” matches this route. The
“{category}” segment is assigned the default value “all”.
Route Dictionary
When the WEB API Framework finds a match for a URI, then it creates a dictionary
that will contain the value for each placeholder. As we know the dictionary contains
the data in the form of a key-value pair. Here, the keys are nothing but the
placeholder names but excluding the curly braces and the values are taken from
the URI path or from the defaults. The dictionary is stored in
the IHttpRouteData object as shown below.
For the URI path “api/student/public“, the route dictionary will contain two
elements such as:
1. controller: “student”
2. category: “all”
For the URI path “api/student/public/cse/101“, the route dictionary will contain
three elements such as:
1. controller: “student”
2. category: “cse”
3. id: “101”
The defaults can also include a value that does not appear anywhere in the route
template. If the route matches, that value is also get stored in the dictionary. For
example:
If the URI path is “api/root/101“, then the dictionary will contain two elements
such as:
1. controller: “Employee”
2. id: “101”
Selecting a Controller
The Controller selection in WEB API is handled by
the IHttpControllerSelector.SelectController method.
HTTP request:
GET http://localhost:50470/api/student/1?version=2.1&details=1
Route Matching
The above URI matches the route named “DefaultApi”. The route dictionary
contains the following elements:
1. controller: “Student”
2. id: “1”
The route dictionary does not contain the query string parameters, “version” and
“details”, but these will still be considered during action selection.
Controller Selection
From the “controller” entry in the route dictionary, the WEB API Framework select
the controller type is StudentController
Action Selection
The above HTTP request is a GET request. The controller actions that support GET
Request are GetAllStudents, GetStudentById, and FindStudentsByName. The route
dictionary does not contain an entry for “action”, so we don’t need to match the
action name.
Next, we need to match the parameter names for the actions, looking only at the
GET actions.
Notice that the version parameter of GetStudentById is not considered, because it is
an optional parameter.
The GetAllStudents method matches trivially. The GetStudentById method also
matches, because the route dictionary contains the “id”. The FindStudentsByName
method does not match.
The GetStudentById method wins because it matches one parameter, versus no
parameters for GetAllStudents. The method is invoked with the following parameter
values:
1. id = 1
2. version = 2.1
Notice that even though the version was not used in the selection algorithm, the
value of the parameter comes from the URI query string.
In Web API1, we had the convention-based routing that defines the routes using the
route templates. When we create a new Web API project the Web API Framework
creates a default route in the WebApiConfig.cs file. The default route is shown
below
So with the above default route and the StudentsController in
place /api/students is mapped to the Get() method of StudentsController as
expected as shown in the below image.
At this point build the solution and navigate to /api/students/1. Notice that, now
you will get the student details whose id=1 and when you navigate
to /api/students/1/courses you will get all the courses into which student with
id=1 is enrolled.
Let us see some of the examples where attribute routing makes it easy.
API versioning
In the below example, the route “/api/v1/students” would be routed to a different
controller than the “/api/v2/students” route.
/api/v1/students
/api/v2/students
Overloaded URI segments
In this example, “1” is an order number, but “pending” maps to a collection.
/orders/1
/orders/pending
Multiple parameter types
In this example, “1” is an order number, but “2013/06/16” specifies a date.
/orders/1
/orders/2013/06/16
How to enable Web API Attribute Routing?
In Web API 2, the Attribute Routing is enabled by default.
The config.MapHttpAttributeRoutes(); code which is present
in WebApiConfig.cs file enables the Web API Attribute Routing.
We can also combine the Web API Attribute Routing with convention-based routing.
To define convention-based routes, call the MapHttpRoute method as shown
below.
This is almost the same as the previous example, but there is a slight difference in
the behavior when the default value is applied.
In the first example (“{stdid?}”), here the default value 1 is directly assigned to
the action method parameter, so the method parameter will have this value exactly.
In the second example (“{stdid=1}”), the default value “1” assigned to the
method parameter through the model-binding process. The default model-binder in
Web API will convert the value “1” to the numeric value 1.
In most of the cases, unless you have custom model binders in your pipeline, the
two forms will be equivalent.
As you can see from the above example, we are using the route attributes at the
action level to define the routes, and furthermore, all the routes in
the StudentsController start with the same prefix – students that mean students
is the common prefix for all the routes available in the Student Controller.
Here, you can set the common prefix “students” for the entire Student Controller
by using the [RoutePrefix] attribute as shown below at the controller level.
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace AttributeRoutingInWEBAPI.Controllers
{
[RoutePrefix("students")]
public class StudentsController : ApiController
{
static List<Student> students = new List<Student>()
{
new Student() { Id = 1, Name = "Pranaya" },
new Student() { Id = 2, Name = "Priyanka" },
new Student() { Id = 3, Name = "Anurag" },
new Student() { Id = 4, Name = "Sambit" }
};
[HttpGet]
[Route]
//This will be translated to /students
public IEnumerable<Student> GetAllStudents()
{
return students;
}
[HttpGet]
[Route("{studentID}")]
//This will be translated to /students/2
public Student GetStudentByID(int studentID)
{
Student studentDetails = students.FirstOrDefault(s => s.Id == studentID);
return studentDetails;
}
[HttpGet]
[Route("{studentID}/courses")]
//This will be translated to /students/2/course
public IEnumerable<string> GetStudentCourses(int studentID)
{
List<string> CourseList = new List<string>();
if (studentID == 1)
CourseList = new List<string>() { "ASP.NET", "C#.NET", "SQL Server" };
else if (studentID == 2)
CourseList = new List<string>() { "ASP.NET MVC", "C#.NET", "ADO.NET" };
else if (studentID == 3)
CourseList = new List<string>() { "ASP.NET WEB API", "C#.NET", "Entity
Framework" };
else
CourseList = new List<string>() { "Bootstrap", "jQuery", "AngularJs" };
return CourseList;
}
}
}
The Route Prefix attribute eliminates the need to repeat the common prefix
“students” on each and every controller action method. However, sometimes we
may need to override the route prefix attribute. Let us understand this with an
example
First, add a class file with the name “Teacher.cs” within the Models Folder. To do
so right-click on the model’s folder, and then add a new class file with the
name “Teacher.cs”. Then Copy and paste the following code in it.
namespace AttributeRoutingInWEBAPI.Models
{
public class Teacher
{
public int Id { get; set; }
public string Name { get; set; }
}
}
At this point build the solution, and navigate to the following URI’s
/students/1
/students/Pranaya
In both the cases we will get the below error:
Multiple actions were found that match the request: GetStudentDetails on
type AttributeRoutingInWEBAPI.Controllers.StudentsController
GetStudentDetails on type
AttributeRoutingInWEBAPI.Controllers.StudentsController
This is because the WEB API Framework does not know or does not identify which
version of the GetStudentDetails() action method to use. This is the situation where
the route constraints play a very important role.
If an integer is specified in the URI like /students/1, then we need to execute the
GetStudentDetails(int studentId) action method which takes an integer parameter
whereas if a string is specified in the URI like /students/Pranaya, then we need to
execute the GetStudentDetails(string studentName) action method which takes the
parameter of type string.
This can be very easily achieved using Attribute Route Constraints in the WEB API
application. To specify the attribute route constraint, the syntax
is “{parameter:constraint}“. With these constraints in place, if the parameter
segment in the URI is an integer, then the GetStudentDetails(int studentId) action
method with integer parameter is invoked and if it is a string value then the
GetStudentDetails(string studentName) action method with string parameter is
invoked.
Let’s modify the Student Controller to use the Attribute Route Constraints
as shown below to achieve the above requirements.
namespace AttributeRoutingInWEBAPI.Controllers
{
[RoutePrefix("students")]
public class StudentsController : ApiController
{
static List<Student> students = new List<Student>()
{
new Student() { Id = 1, Name = "Pranaya" },
new Student() { Id = 2, Name = "Priyanka" },
new Student() { Id = 3, Name = "Anurag" },
new Student() { Id = 4, Name = "Sambit" }
};
[HttpGet]
[Route("{studentID:int}")]
public Student GetStudentDetails(int studentID)
{
Student studentDetails = students.FirstOrDefault(s => s.Id == studentID);
return studentDetails;
}
[HttpGet]
[Route("{studentName:alpha}")]
public Student GetStudentDetails(string studentName)
{
Student studentDetails = students.FirstOrDefault(s => s.Name == studentName);
return studentDetails;
}
}
}
Now build the solution, and navigate to the following two URIs and see everything is
working as expected.
/students/1
/students/Pranaya
Please note that “alpha” stands for uppercase or lowercase alphabet characters.
Along with alpha and int, you can also use constraints such as decimal, float, long,
double, bool, etc. Please check the following MSDN link for the full list of available
constraints in web API.
https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-
routing-and-actions/attribute-routing-in-web-api-2#route-constraints
Example:
If you want GetStudentDetails(int studentId) action method to be mapped to
URI /students/{studentId}, only if the studentId is a number greater than ZERO,
then use the “min” constraint as shown below.
With the above change, if we specify a positive number like 1 in the URI, then it will
be mapped to the GetStudentDetails(int studentID) action method as expected
/students/1
However, if we specify 0 or a negative number less than ZERO, then we will get an
error. For example, if we specify 0 as the value for studentID in the URI,
/students/0
We will get the below error
Along with the “min” constraint, you can also specify the “max” constraint as
shown below. For example, if you want the studentID value in the URI to be
between 1 and 3 inclusive, then you can specify both “min” and “max” constraints
as shown below.
The above example can also be achieved using the “range” attribute as
shown below
Now you can apply the custom constraint in your routes as shown below.
[HttpGet]
[Route("{studentName:alpha}")]
public Student GetStudentDetails(string studentName)
{
Student studentDetails = students.FirstOrDefault(s => s.Name == studentName);
return studentDetails;
}
Then click on the execute button. It will give us the below result.
Route Order
When the WEB API Framework tries to match a URI with a route, it evaluates the
routes in a particular order. To specify the order, set the Order property on the
route attribute. Lower values are evaluated first. The default order value is zero.
Here is how the total ordering is determined:
1. Compare the Order property of the route attribute.
2. Look at each URI segment in the route template. For each segment, order as
follows:
3. Literal segments.
4. Route parameters with constraints.
5. Route parameters without constraints.
6. Wildcard parameter segments with constraints.
7. Wildcard parameter segments without constraints.
8. In the case of a tie, routes are ordered by a case-insensitive ordinal string
comparison (OrdinalIgnoreCase) of the route template.
Here is an example. Suppose you define the following controller: