Conventional Routing VS Attribute Routing

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 the 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 an 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 the “images” folder and then access the “john.aspx” file having ID 2. Hence it is not a 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 a 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 residing 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.

public class RouteConfig  
{  
    public static void RegisterRoutes(RouteCollection routes)  
    {  
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
   
        routes.MapRoute(  
            name: "Default",  
            url: "{controller}/{action}/{id}",  
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }  
        );  
    }  
}

The Global.asax file looks like this.

protected void Application_Start()  
{  
    //Some other code is removed for clarity.  
    RouteConfig.RegisterRoutes(RouteTable.Routes);  
}

Requested URL

The Figure illustrates 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.

public interface IRouteHandler  
{  
    IHttpHandler GetHttpHandler(RequestContext requestContext);  
}

After finding the route successfully, the 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 the 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.

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

The 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 other!

Name of Route

First of all, I want to say, that there are very ambiguous ideas in the community about the 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 it 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 based on Name, it happens based on its URL pattern. 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 it is the thing that creates uniqueness for the URL generation. Let’s understand it practically.

Make a controller having two action methods.

public class HomeController : Controller  
{   
    public ActionResult Index()  
    {  
        return View();  
    }  
  
    public ActionResult Student()  
    {  
        return View();  
    }  
}

Create routes for understanding Route Name.

public static void RegisterRoutes(RouteCollection routes)  
{  
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
   
    // Specific Route  
    routes.MapRoute(  
        name: "SecondRoute",  
        url: "Home/Student",  
        defaults: new { controller = "Home", action = "Student" }  
    );  
   
    //Generic Route  
    routes.MapRoute(  
        name: "FirstRoute",  
        url: "{controller}/{action}/{id}",  
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }  
    );  
}

Now we have to create the Views.

Student.cshtml

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

Index.cshtml

@{  
    ViewBag.Title = "Second Route";  
}  
<h2>First Route</h2>  
   
<a href="@Url.RouteUrl("SecondRoute")">This URL will direct you to Second Route.</a>

Now when we execute the application the default generic route will come into action.

First route

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

Second route

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

So the conclusion is, that 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.

Server error

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.

Pattern of the route

Literal means a “fixed” value and variable placeholder means “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 placeholder in a segment with the help of a literal. For examples, see the figure.

Url pattern


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

Defaults of Route

When you define a route, you can assign default values for each segment of the URL, these default values come into action when no parameter is provided in the URL. You can set default 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 that route will not work. And request goes to a route that has default parameters. You add constraints to the route to ensure that it will work according to your application requirements. You will see more detail on this topic.

Namespaces to the Route

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

Default Route and Custom Route

{controller}/{action}/{id}

Default route

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, and 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 named Student.

public class HomeController: Controller  
{  
    public string Index()  
    {  
        return "This is Index action method";  
    }  
    public string Student(int rollno)  
    {  
        return $"Roll Number is {rollno}”;  
    }  
}

Now we have to create a custom route in the RoutConfig class

public static void RegisterRoutes(RouteCollection routes)  
{  
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
    routes.MapRoute(  
             name: "FirstRoute",  
             url: "Home/{action}/{rollno}",  
             defaults: new { controller = "Home", action = "Student", rollno = UrlParameter.Optional }  
             );  
    routes.MapRoute(  
            name: "Default",  
            url: "{controller}/{action}/{id}",  
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }  
        );  
}

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. The routing flow is as follows.

Routing flow

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

URL pattern

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

Query String has a 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 hackers or penetrators. Better than a query string, the way of passing data is:

https://Server/Home/Student/12345

What is the purpose of the IgnoreRoute() method?

In the above code of the 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.

public class RouteCollection : Collection<RouteBase>  
{  
    //other code is removed for clarity  
    public bool RouteExistingFiles { get; set; }  
}

You can see in the above code, that the datatype of the 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 the RouteConfig class. Otherwise, when this property is set to false, UrlRoutingModule will never handle any request.

Now, the problem is, that when you have set the RouteExistingFiles property to true, the routing module handles 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 ignore route method will stop accessing the routes defined in IgnoreRoute parameters. The method looks like.

public static void IgnoreRoute(string url); 

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

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

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

The above code of IgnoreRoute() shows that resource. and files are banned from 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 that route will not work.

Let’s understand it practically, 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.

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

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 are defined in the figure below,

Escape sequence

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 literally gives us great readability. Hence in this way, you can make your 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?

  1. First of all, look at our previous custom route.
    public static void RegisterRoutes(RouteCollection routes)  
    {  
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
       
        routes.MapRoute(  
                 name: "FirstRoute",  
                 url: "Home/{action}/{rollno}",  
                 defaults: new { controller = "Home", action = "Student", rollno = UrlParameter.Optional }  
                 );  
        routes.MapRoute(  
                name: "Default",  
                url: "{controller}/{action}/{id}",  
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }  
            );  
    }
  2. 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.
  3. You have to take care of the URL pattern in the RouteConfig class as well as in the Controller where your request will be handled. So this too and fro movement in between these folders creates complexity for you.
  4. It will reduce the chances of errors because in the RouteConfig class, there can be any mistake while creating a new custom route
  5. It is easy to use and helps in mapping the same action method with two routes.
  6. 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,

public class RouteConfig  
{  
    public static void RegisterRoutes(RouteCollection routes)  
    {  
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
      
        // below a line of code will enable the attribute routing.  
        routes.MapMvcAttributeRoutes();  
    }  
}

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

public class HomeController : Controller  
{  
    [Route("Home/Student/{rollno}")]  
    public string Student(int rollno)  
    {  
        return $"Name is {rollno}";  
    }  
}

Output

URL pattern

How to make an 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 of the parameter like below.

public class HomeController: Controller  
{  
    [Route("Home/Student/{rollno ?}")]  
    public string Student(int rollno)  
    {  
        return $"Name is {rollno}";  
    }  
}
public class HomeController : Controller    
{    
    [Route("Home/Student/{rollno = 23}")]    
    public string Student(int rollno)    
    {    
        return $"Name is {rollno}";    
    }    
}

Output

URL pattern

What is RoutePrefix?

You may see that many routes have the same portion from their start, which 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 the RoutePrefix attribute. This attribute will be set at the controller level. As shown below,

[RoutePrefix("Home")]  
public class HomeController : Controller  
{  
    [Route("Student/{rollno= 23}")]  
    public string Student(int rollno)  
    {  
        return $"Roll Number {rollno}";  
    }  

    [Route("Teacher")]  
    public string Teacher()  
    {  
        return "Teacher’s method";  
      
    }  
}

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

URL student and teacher

So when you want to override the route prefix then use the ~ 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?

[RoutePrefix("Home")]  
public class HomeController : Controller  
{  
    [Route("Student/{rollno= 23}")]  
    public string Student(int rollno)  
    {  
        return $"Roll Number is {rollno}";  
    }  

    [Route("~/Home2/Teacher")]  
    public string Teacher()  
    {  
        return "Teacher’s method";  

    }  
}

Student and teacher

Yes! You can do it by using the [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 have seen in Convention-based Routing, we can set the default action method.

[RoutePrefix("Home")  
[Route("{action = Teacher}")]  
public class HomeController: Controller  
{  
    public string Teacher()  
    {  
        return "Teacher’s method";  
    }  
}

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

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

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

Yes!

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

Syntax

[Route("URLPath", Name = "RouteName")] 
[Route("Home/Student", Name = "StudentRoute")]  
public ActionResult Student()  
{  
    return View();  
}

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

As you had seen in the 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 the Order parameter in the route attribute. Suppose we have a controller having two action methods named First and Second. And they have Order parameters 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).

public class HomeController : Controller  
{  
    [Route(Order = 2)]  
    public string First()  
    {  
        return "This is First action method having order 2";  
    }  
   
    [Route(Order = 1)]  
    public string Second()  
    {  
        return "This is Second action method having order 1";  
    }   
}

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

Action method

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 have 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.

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

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

Different constraints

Let’s look at its practical example.

public class HomeController : Controller  
{  
    [Route("Home/Student/{rollno:maxlength(2)}")]  
    public string Student(int rollno)  
    {  
        return $"Roll Number's length is {rollno}";  
    }  
}

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

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

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

Student

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 queries then feel free to contact me in the comments section. Also, giving feedback, either positive or negative, will help me to make my articles better and increase my enthusiasm to share my knowledge.


Similar Articles