Convention Routing VS Attribute Routing

Routing is the first and foremost phenomenon in the ASP.NET MVC pipeline. Here, you will learn about routing, it's working, and its variants.

Introduction

Routing is the first and foremost phenomenon in the ASP.NET MVC pipeline. Here, you will learn about routing, how it works,  and its variants.

Background

Last week one of my friends asked this question: "Can we make our custom route with some constraints and what is attribute routing?" I am dedicating this article to him. I hope he will like this.

The topics to be covered are,

  1. Routing and how it works

    1. Parts of a Route
    2. Default Route and Custom Route
    3. What is the purpose IgnoreRoute method?
    4. How to apply constraints to Custom Route?

  2. What is Attribute Routing?

    1. Default and optional parameter
    2. Default action and RoutePrefix
    3. Name and Order property
    4. Attribute Routing including Constraints

Routing

Routing is the first step in ASP.NET MVC pipeline. This is the replacement of the concrete, physical files used in the URLs. In other words, routing is the phenomenon in which controller and actions execute rather than the concrete physical files.

Why do we need routing?

Traditionally, the URL of the browser represents the physical file. A file can be HTML file, ASPX page etc. Let’s understand this traditional way,

www.abcd.com/images/john.aspx?id=2

This URL shows that first we go to “images” folder then access the “john.aspx” file having id is 2. Hence it is not the good way to expose your precious information to the URLs of the browser, so with these URLs sites can be hacked. As a result, Routing comes into action and solves this problem of the traditional file-based system. Implementation of routing starts with route table. The route table is a collection of all possible, correct routes that can be used to map the HTTP request URLs. Let’s understand RouteTable and the working of Routing in detail.

Routing is a pattern matching system that matches the incoming request to the registered URL patterns reside in the Route Table. When an ASP.NET MVC application starts, it registers patterns to the RouteTable to tell the routing engine to give a response to the requests that match these patterns. An application has only one RouteTable and it resides in the Application_Start event of Global.asax of the application. The routes are registered to the RouteConfig class that has the route URLs to be mapped and ignored in the application. Let's have a look at it,

  1. public class RouteConfig  
  2.     {  
  3.         public static void RegisterRoutes(RouteCollection routes)  
  4.         {  
  5.             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
  6.    
  7.             routes.MapRoute(  
  8.                 name: "Default",  
  9.                 url: "{controller}/{action}/{id}",  
  10.                 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }  
  11.             );  
  12.         }  
  13.     }  

The Global.asax file looks like,

  1. protected void Application_Start()  
  2.         {  
  3.         //Some other code is removed for clarity.  
  4.             RouteConfig.RegisterRoutes(RouteTable.Routes);  
  5.         }  
  6.     }  

ASP.NET

The Figure is illustrating the working of routes and depicts  how the routing engine processes URL requests and gives the response to the browser.

When the UrlRoutingModule finds a perfect matching route for the incoming URL from the RouteTable then Routing Engine forwards this request to RouteHandler. When it matches successfully, then IRouteHandler comes into action for that route and its GetHttpHandler() method is invoked. IRouteHandler is looking like this,

  1. public interface IRouteHandler  
  2.     {  
  3.         IHttpHandler GetHttpHandler(RequestContext requestContext);  
  4.     }  

After finding the route successfully, ProcessRequest() method is invoked, as shown in the figure; otherwise if the requested URL doesn’t match with any pattern in the RoutTable then the user will be redirected to HTTP 404 error page.

Parts of a Route

When you are going to register the routes you have to use the overloaded version of the MapRoute method in the RouteConfig class. There are 6 overloaded versions of the MapRoute method, the last method having all parameters is explained below,

  1. public static class RouteCollectionExtensions  
  2. {  
  3.     public static Route MapRoute(this RouteCollection routes, string name,  string url, object defaults, object constraints, string[] namespaces);  
  4. }  

Above MapRoute method has the following parameters,

  • Name of the Route
  • URL pattern of the Route
  • Defaults of the Route
  • Constraints on the Route
  • Namespaces to the Route

Let’s understand each!

Name of Route

First of all, I want to say, there are very ambiguous ideas in the community about Name of the Route. So I highly recommend reading this section carefully and please leave your comment about this because I have learned it myself. So, please correct me if I'm wrong.

The route that is registered in the RouteTable must have a unique name to differentiate from other routes. This name refers to a specific URL pattern in the RouteTable. The most important thing is that this name is only used for URL generation. Hence I concluded that Routing does not happen on the basis of Name, it happens on the basis of its URL pattern. In fact, URL pattern tells the UrlRoutingModule what to do with the request, not the name of the route. Then the question comes why it should be unique, it should be because of it the thing that creates uniqueness for the URL generation. Let’s understand it practically.

Make a controller having two action methods.

  1.   public class HomeController : Controller  
  2.   {   
  3. public ActionResult Index()  
  4.       {  
  5.           return View();  
  6.       }  
  7.   
  8.       public ActionResult Student()  
  9.       {  
  10.           return View();  
  11.       }  
  12.   }  
Create routes for understanding Route Name.

  1. public static void RegisterRoutes(RouteCollection routes)  
  2.         {  
  3.             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
  4.    
  5.             // Specific Route  
  6.             routes.MapRoute(  
  7.                 name: "SecondRoute",  
  8.                 url: "Home/Student",  
  9.                 defaults: new { controller = "Home", action = "Student" }  
  10.             );  
  11.    
  12.             //Generic Route  
  13.             routes.MapRoute(  
  14.                 name: "FirstRoute",  
  15.                 url: "{controller}/{action}/{id}",  
  16.                 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }  
  17.                 );  
  18.         }  
Now we have to create the Views.

Student.cshtml

  1. @{  
  2.     ViewBag.Title = "Second Route";  
  3. }   
  4. <h2>Second Route</h2>  
  5. This is our 2nd Route.  

Index.cshtml

  1. @{  
  2.     ViewBag.Title = "Second Route";  
  3. }  
  4. <h2>First Route</h2>  
  5.    
  6. <a href="@Url.RouteUrl("SecondRoute")">This URL will direct you to Second Route.</a>  
Now when we execute the application then default generic route will come into action

ASP.NET

When we click on URL then it will direct us to View of Second Route.

ASP.NET

As you can see, we pass the name of route “Second Route” in the ancher tag and while clicking it will redirect us to that route whose name is given in ancher tag.

So the conclusion is, route names are used for URL generation. If the route name is not unique then how UrlRoutingModule will know about the Second Route and it will throw a runtime error as shown below,

ASP.NET

URL Pattern of the Route

URL pattern of the route consists of literal values and variable placeholders. These variables are also called URL parameters. All these literal values and placeholders are separated by forwarding slash (/) and called Segments.

ASP.NET

Literal means a “fixed” value and variable placeholder mean “replacement of some value”. In a URL pattern, you can define a placeholder with the help of curly braces {}. You can define more than one placeholders in a segment with the help of literal. For examples, see  the figure.


Hence, if you want to add more than one placeholders in a segment then you have to use a literal between those placeholders.

Defaults of Route

When you define a route, you can assign defaults values for each segment of the URL, these default values come into action when no parameter is provided in the URL. You can set defaults values for the route by making the anonymous object of the RouteValueDictionary class.

Constraints to Route

Constraints are limitations on Route parameters. When you set any constraint to a route then if the URL consists of values that do not fulfill the constraint requirements then that route will not work. And request goes to route that has defaults parameters. You add constraints to route to ensure that it will work according to your application requirements. You will see its more detail on this topic.

Namespaces to the Route

You can set your own namespaces for the web application. Namespaces can be added into routes by making objects of RouteValueDictionary class. This parameter used when you want to redirect your URL to a specific controller having a specific namespace.

Default Route and Custom Route

  1. {controller}/{action}/{id}  

ASP.NET

The figure illustrates that a route is going to be registered in the RouteCollection (routes.RouteTable) having a unique name Defaults, a URL pattern in which controller, action, id all are placeholders, then there is a defaults property whose responsibility is to  initialize the controller and action automatically if it doesn’t include the requested URL.

Custom Route

Let’s create a custom route to understand Routing. As we have HomeContoller and the Index action method. Let’s create another action method name as Student,

  1. public class HomeController: Controller  
  2. {  
  3.     public string Index()  
  4.     {  
  5.         return "This is Index action method";  
  6.     }  
  7.     public string Student(int rollno)  
  8.     {  
  9.         return $"Roll Number is {rollno}”;  
  10.         }  
  11. }  

Now we have to create a custom route in RoutConfig class

  1. public static void RegisterRoutes(RouteCollection routes)  
  2. {  
  3.     routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
  4.     routes.MapRoute(  
  5.              name: "FirstRoute",  
  6.              url: "Home/{action}/{rollno}",  
  7.              defaults: new { controller = "Home", action = "Student", rollno = UrlParameter.Optional }  
  8.              );  
  9.     routes.MapRoute(  
  10.             name: "Default",  
  11.             url: "{controller}/{action}/{id}",  
  12.             defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }  
  13.         );  
  14. }  

Before going to execute the application I want to tell you a very crucial thing about Routing, that is, “Always define your routes from Specific to General”. In the above code, the default route is most general so it should be at the end of the RouteTable. And our most specific route is the first route that we defined earlier. Routing flow is as follows,

ASP.NET

Now when you execute your application and write the URLs as given below then the output is as follows,

 

The table illustrates a clear big picture of URL patterns and its output. The error in the second URL is because of the absence of parameter value in URL. On the other hand when you pass Parameter value in the third URL then that route comes into action and show the output. This type of data passing in the URL is called Query String method.

Query String has question mark sign (?) in its URL. But this way of passing data is not a good way because through this method your data can be exposed to the hackers or penetrators. Better than a query string, the way of passing data is:

https://Server/Home/Student/12345

What is the purpose of IgnoreRoute() method?

In the above code of RouteConfig class you can see the IgnoreRoute() method as shown below:

As you know, the URL pattern {controller}/{action}/{id} is handled by UrlRoutingModule by default. But the point of interest is if you want the RoutingModule to handle the above-mentioned pattern then you have to set the RouteExistingFiles property of the RouteCollection to true.  

  1. public class RouteCollection : Collection<RouteBase>  
  2. {  
  3.     //other code is removed for clarity  
  4.     public bool RouteExistingFiles { get; set; }  
  5. }  

You can see in the above code, the datatype of RouteExistingFiles property is bool so it can be true or false. When it is true, then the RoutingModule will handle all the requests that match the defined URL pattern in RouteConfig class. Otherwise, when this property is set to false, UrlRoutingModule will never handle any request.

Now, the problem is, when you have set RouteExistingFiles property to true, routing module handle all the requests. But when you want to stop the access of some routes to the browser, then how can you do that?

This can be done by using IgnoreRoute() method. The ignoreroute method will stop accessing the routes defined in IgnoreRoute parameters. The method looks like,

  1. public static void IgnoreRoute(string url);  

 This will ignore the specified URL route from the list of available routes in RouteTable.

Now in RouteConfig class, what does the following code means?

  1. routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  

The above code of IgnoreRoute() shows that resource.axd files are ban for the access. The last point is, these .axd files are not present in our project, and these are reserved for HTTP handling.

How to apply constraints to Custom Route?

Constraints are limitations on Route parameters. When you set any constraint to a route then if the URL consists of values that do not fulfill the constraint requirements then that route will not work.

Let’s understand it in a practical way, suppose you want to limit the roll number of the student to only 5 digits, then how can you do that?

This can be done with the help of constraints. Let’s play with it.

  1. routes.MapRoute(  
  2.                 name: "FirstRoute",  
  3.                 url: "Home/{action}/{rollno}",  
  4.                 defaults: new { controller = "Home", action = "Student", rollno = UrlParameter.Optional },  
  5.                 constraints: new { rollno = @"\d{5}" }  
  6.                 );  

As shown above, roll number digits must be 5. When we execute the following URL:

https://Home/Student/12345

Output:    Roll Number is 12345

@”\d{5}” has @ sign as prefix which is called verbatim literal. Its opposite and bad way is using Escape sequences. Escape sequences and their use is defined in figure below,


Let’s know about the difference between both:

  • With Escape Sequence: “C:\\Computer\\Lectures\\ASP.NET”
  • With Verbatim Literal: @“C:\Computer\Lectures\ASP.NET”

As we can see verbatim literal gives us great readability. Hence in this way you can make your own constraints on your custom routes.

What is Attribute Routing?

Attribute routing is the new type of routing in ASP.NET MVC 5. According to the name Attribute Routing, one can suppose that this type of methodology will use Attribute to define routes. Yes!

Let’s understand it in detail.

Why do we need Attribute Routing?

First of all, look at our previous custom route

  1. public static void RegisterRoutes(RouteCollection routes)  
  2. {  
  3.     routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
  4.    
  5.     routes.MapRoute(  
  6.              name: "FirstRoute",  
  7.              url: "Home/{action}/{rollno}",  
  8.              defaults: new { controller = "Home", action = "Student", rollno = UrlParameter.Optional }  
  9.              );  
  10.     routes.MapRoute(  
  11.             name: "Default",  
  12.             url: "{controller}/{action}/{id}",  
  13.             defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }  
  14.         );  
  15. }  
  1. In the above code there are only two routes defined with the help of MapRoute. But when our application is large enough to have more than two routes, then it will become very complex to make each route separately. For each route, you have to write 5 lines so it will consume your time and disturb your development time.
  2. You have to take care of the URL pattern in RouteConfig class as well as in Controller where your request will be handled. So this too and fro movement in between these folders creates complexity for you.
  3. It will reduce the chances of errors because in RouteConfig class there can be any mistake while creating a new custom route
  4. It is easy to use and help in mapping the same action method with two routes.
  5. You don’t have to take care of the routing flow i.e, from most specific to most general. All here is the attribute you use the action method.

Now let’s move to use Attribute Routing.

First of all, you have to enable the access to attribute routing. So RouteConfig class becomes,

  1. public class RouteConfig  
  2.     {  
  3.         public static void RegisterRoutes(RouteCollection routes)  
  4.         {  
  5.             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
  6.       
  7.         // below a line of code will enable the attribute routing.  
  8.             routes.MapMvcAttributeRoutes();  
  9.         }  
  10.     }  

Now, let’s go into controller and take a look at its working. A [Route] attribute is used at the top of the action method.

  1. public class HomeController : Controller  
  2.     {  
  3.         [Route("Home/Student/{rollno}")]  
  4.         public string Student(int rollno)  
  5.         {  
  6.             return $"Name is {rollno}";  
  7.         }  
  8.     }  

Output is,

 
 
How to make optional parameter and set the default value of a parameter in Attribute Routing?

If you want to make a parameter optional in attribute routing then simply place a question mark at the end or parameter like below,

  1. public class HomeController: Controller  
  2.     {  
  3.         [Route("Home/Student/{rollno ?}")]  
  4.         public string Student(int rollno)  
  5.         {  
  6.             return $"Name is {rollno}";  
  7.         }  
  8.     }  

  1. public class HomeController : Controller  
  2.    {  
  3.        [Route("Home/Student/{rollno = 23}")]  
  4.        public string Student(int rollno)  
  5.        {  
  6.            return $"Name is {rollno}";  
  7.        }  
  8.    }  

Output is,

 
 
What is RoutePrefix?

You may see that many routes have the same portion from its start, it means their prefixes are the same. For example:

  • Home/Student
  • Home/Teacher

Both the above URLs have the same prefix which is Home.

So, rather than repeatedly typing the same prefix, again and again, we use RoutePrefix attribute. This attribute will be set at the controller level. As shown below,

  1. [RoutePrefix("Home")]  
  2.     public class HomeController : Controller  
  3.     {  
  4.         [Route("Student/{rollno= 23}")]  
  5.         public string Student(int rollno)  
  6.         {  
  7.             return $"Roll Number {rollno}";  
  8.         }  
  9.    
  10.         [Route("Teacher")]  
  11.         public string Teacher()  
  12.         {  
  13.             return "Teacher’s method";  
  14.           
  15.         }  
  16.     }   

In the above code, RoutePrefix is set to controller level and on action methods, we don’t have to use Home prefix again and again. The output is as follows:

 
So when you want to override the route prefix then use ~ sign, as shown below, RoutePrefix has held on the whole controller. But when you want to execute an action that shouldn’t use the prefix as the prefix of the controller, then what will you do?
  1. [RoutePrefix("Home")]  
  2.     public class HomeController : Controller  
  3.     {  
  4.         [Route("Student/{rollno= 23}")]  
  5.         public string Student(int rollno)  
  6.         {  
  7.             return $"Roll Number is {rollno}";  
  8.         }  
  9.    
  10.         [Route("~/Home2/Teacher")]  
  11.         public string Teacher()  
  12.         {  
  13.             return "Teacher’s method";  
  14.    
  15.         }  
  16.     }  

 


Yes! You can do it by using [Route] attribute at the top of the controller. Look at the code below. Can we set the default action method in attribute routing? As you had seen in Convention-based Routing, we can set the default action method.

 

  1. [RoutePrefix("Home")  
  2. [Route("{action = Teacher}")]  
  3. public class HomeController: Controller  
  4. {  
  5.     public string Teacher()  
  6.     {  
  7.         return "Teacher’s method";  
  8.     }  
  9. }  

The second Route attribute at the top of controller sets the default value for an action method.

As you had seen in the routing section, we can set Name for the route. So,

Can we set the Name in attribute routing for specific URL?

Yes!

Here also the name is only for URL generation. We can set the name as,

Syntax:

  1. [Route("URLPath", Name = "RouteName")]  

  1. [Route("Home/Student", Name = "StudentRoute")]  
  2.      public ActionResult Student()  
  3.      {  
  4.          return View();  
  5.      }  

The above code shows how can we set the name of the route in attribute routing. After reading the section of Route Names in convention-based routing, you can repeat the procedure of URL generation in this Route attribute.

As you had seen in routing section, we should create our routes from more specific to more general flow. Then how can we do that in attribute routing?

Can we set the preference of routes in attribute routing?

Yes! You have to use Order parameter in route attribute. Suppose we have a controller having two action methods named as First and Second. And they have Order parameter set to 1 and 2.

The action method will execute according to FCFS (First Come First Server) and its value can be from -2,147,483,648 to 2,147,483,647 (size of int).

  1. public class HomeController : Controller  
  2.    {  
  3.     [Route(Order = 2)]  
  4.         public string First()  
  5.         {  
  6.             return "This is First action method having order 2";  
  7.         }  
  8.    
  9.         [Route(Order = 1)]  
  10.         public string Second()  
  11.         {  
  12.             return "This is Second action method having order 1";  
  13.         }   
  14.     }  

 When we run this code, our Second action method will execute because it has higher order.

ASP.NET

But if no order is specified in the Route attribute then action methods will execute according to the order in which the routes are registered in the RouteTable.

As you had seen in the routing section, we can set constraints to the convention-based routing.

Can we set the constraints for attribute routing? If yes then how?

Yes! You can set the constraints in attribute routing. The syntax is as follows,

  1. [Route(URLPath/{parameterName: constrain})]  

First of all, take a look at different constraints that can be used in attribute routing.

 
 
Let’s look at its practical example. 

  1. public class HomeController : Controller  
  2.    {  
  3.     [Route("Home/Student/{rollno:maxlength(2)}")]  
  4.         public string Student(int rollno)  
  5.         {  
  6.             return $"Roll Number's length is {rollno}";  
  7.         }  
  8.     }  

http://localhost:50656/Home/Student/12

http://localhost:50656/Home/Student/123

  1. [Route("Home/Student/{rollno:maxlength(2):range(9,10)}")]  
  2.         public string Student(int rollno)  
  3.         {  
  4.             return $"Roll Number's length is {rollno}";  
  5.         }  

 

Hence these are limitations to our routes using attribute routing.
 
Conclusion
 
I hope this article has helped you in understanding all the concepts. If you have any queryiesthen feel free to contact me in the comments section. Also, give feedback, either positive or negative, it will help me to make my articles beter and increase my enthusiasm to share my knowledge.

G
M
T
 
Text-to-speech function is limited to 200 characters