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,
- Routing and how it works
- Parts of a Route
- Default Route and Custom Route
- What is the purpose IgnoreRoute method?
- How to apply constraints to Custom Route?
- What is Attribute Routing?
- Default and optional parameter
- Default action and RoutePrefix
- Name and Order property
- Attribute Routing including Constraints
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,
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,
protected void Application_Start() { //Some other code is removed for clarity. RouteConfig.RegisterRoutes(RouteTable.Routes); }
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,
public interface IRouteHandler { IHttpHandler GetHttpHandler(RequestContext requestContext); }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,
public static class RouteCollectionExtensions { public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces); }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
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.
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.
@{ ViewBag.Title = "Second Route"; }Index.cshtmlSecond Route
This is our 2nd Route.
@{ ViewBag.Title = "Second Route"; }Now when we execute the application then default generic route will come into actionFirst Route
This URL will direct you to Second Route.
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,
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.
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 fulfil 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
- {controller}/{action}/{id}
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,
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 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. Routing flow is as follows,
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.
public class RouteCollection : Collection(RouteBase) { //other code is removed for clarity public bool RouteExistingFiles { get; set; } }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,
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?
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.
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 is defined in figure below,
- With Escape Sequence: “C:\\Computer\\Lectures\\ASP.NET”
- With Verbatim Literal: @“C:\Computer\Lectures\ASP.NET”
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
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 } ); }
- 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.
- 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.
- It will reduce the chances of errors because in RouteConfig class there can be any mistake while creating a new custom route
- It is easy to use and help in mapping the same action method with two routes.
- 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.
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 controller and take a look at its 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 is,
public class HomeController : Controller { [Route("Home/Student/{rollno ?}")] public string Student(int rollno) { return $"Name is {rollno}"; } }And if you want to set the default value of any parameter then you have to use this pattern:
ParameterName = Value, as shown in figure:
public class HomeController : Controller { [Route("Home/Student/{rollno = 23}")] public string Student(int rollno) { return $"Name is {rollno}"; } }Output is,
- Home/Student
- Home/Teacher
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,
[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 Home prefix again and again. The output is as follows:
[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"; } }
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.
[RoutePrefix("Home") [Route("{action = Teacher}")] public class HomeController : Controller { public string Teacher() { return "Teacher’s method"; } }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,
[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 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).
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 higher order.
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,
[Route(URLPath/{parameterName: constrain})]First of all, take a look at different constraints that can be used in attribute routing.
public class HomeController : Controller { [Route("Home/Student/{rollno:maxlength(2)}")] public string Student(int rollno) { return $"Roll Number's length is {rollno}"; } }
[Route("Home/Student/{rollno:maxlength(2):range(9,10)}")] public string Student(int rollno) { return $"Roll Number's length is {rollno}"; }Output is:
No comments:
Post a Comment