URL Routing in MVC4

We’ll try to demonstrate how the routing works in a MVC4 application. For this we’ll take an example.

Routing is the process of mapping the controller and the action method through which the view could be presented to the user as an output. Routing works on the URL, or rather we can say the restfull URL, that the ASP.NET MVC understands and correspondingly at runtime it attempts to load the controller and executes the action method specified in the URL that in turn dynamically loads the view (if any) for that action method.

In traditional ASP.NET we have physical files on the application server's hard disk where it was hosted, that responds to the user's request. Whenever a user makes a request for a page, the ASP.NET Engine loads the page and tries to render it to the user's browser after following a full-fledged page life cycle. Since both the .aspx page and .aspx.cs file are tightly coupled, so whenever a request comes for a specific page, the ASP.NET Engines load both the .aspx and .aspx.cs files. This we can also state as the .aspx.cs file is the controller and the .aspx file is the view as in a traditional ASP.NET application. So it actually locates the physical file on the server's hard disk where it is hosted.

We'll try to demonstrate how the routing works in a MVC4 application. For that we'll use an example. Let's proceed with the example. I've named my application “UrlRoutingDemo” by selecting the “Basic” as the MVC4 project template.

Once the application is loaded in Visual Studio check in the Solution Explorer and we'll have a folder named “App_Start” that contains a file named “RouteConfig.cs”. The contents of which would be the default as in the following:

  1.       protected void Application_Start()  
  2.       {  
  3.           AreaRegistration.RegisterAllAreas();  
  4.   
  5.           WebApiConfig.Register(GlobalConfiguration.Configuration);  
  6.           FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);  
  7.           RouteConfig.RegisterRoutes(RouteTable.Routes);  
  8.           BundleConfig.RegisterBundles(BundleTable.Bundles);  
  9.       }  
It contains only one single function named RegisterRoutes that accepts a RouteCollection class object. One route is added to the RouteCollection object using the MapRoute function with the name “Default” (the one highlighted in yellow) also the pattern for the URL is specified as {Controller}/{action}/{id} where the default values for the controller and action are specified under the defaults named argument (which is the third parameter to the MapRoute function). Also for the id element the default value optional is specified.

Let's try to understand this Route in depth. From the code snippet we can state that the route has the following 3 segments.

 

  1. Controller Segment (which is defined as {controller})
  2. Action Segment (which is defined as {action})
  3. Parameter Segment (which is defined as {id})

We can also state that the default or optional values are provided for all the three segments. Like the Controller havs a default value of “Home”, the action has a default value of “Index” and id has an optional value.

NOTE

  1. You can change the default values of the controller and the action method to any value of what your application requires. You can also add your own routing configuration in the RouteConfig.cs file as per your needs.
  2. Also the name of the controller and the action method is not case sensitive. In other words, if the name of your Contoller is “Home” and in the routeconfig file you specified it as “home”. It will work.

This route can handle any request that has a maximum of 3 segments viz {controller}/{action}/{id}. It is basically a combination of default and dynamic route. The following table would explain the line as stated in a more concise manner.

Table 1.0



The process of registering the routes with the application is done/called from the Global.asax file in the Application_Start function. This event is called when the application is started. You can check the details about that in the following code snippet.

  1. protected void Application_Start()  
  2.       {  
  3.           AreaRegistration.RegisterAllAreas();  
  4.   
  5.           WebApiConfig.Register(GlobalConfiguration.Configuration);  
  6.           FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);  
  7.           RouteConfig.RegisterRoutes(RouteTable.Routes);  
  8.           BundleConfig.RegisterBundles(BundleTable.Bundles);  
  9.       }  
Let's try to add 2 controllers to the application. I named my first controller as “Home” and the second one as “Employee”. Here is the code snippet for both the files.

“HomeController.cs”
  1. public ActionResult Index()  
  2.         {  
  3.             ViewBag.Message = "Index Function Called From HomeController";  
  4.             return View("_ViewResult");  
  5.         }  
“EmployeeController.cs”
  1. public ActionResult Index()  
  2.         {  
  3.             ViewBag.Message = "Index Function Called From EmployeeController";  
  4.             return View("_ViewResult");  
  5.         }  
Both of them just contains the Index function that has a dynamic variable named Message of ViewBag that holds a certain value. And both of them are returning a shared view whose name is “_ViewResult”.

The following is the code snippet for the view “_ViewResult”.
  1. @{  
  2.     ViewBag.Title = "_ViewResult";  
  3. }  
  4. <h2>  
  5.     Learning URL Routing Feature OF MVC4</h2>  
  6. <h2>  
  7.     @ViewBag.Mesasge  
  8. </h2>  
It just displays the message that comes from the controller.

If you run the application now, it will display the details for the “Home” controller (because of the routing configuration that set the default value of the controller to “Home” and action to “Index”). By looking at your browser's URL we can map this to the first row of the Table 1.0.

As I said earlier we can define our own routing mapping as per our needs. The following could be the possible reasons for defining our own custom routing mappings in the RouteConfig.cs file.

I. Suppose you define a prefix to the URL so that it enables us to track the users requesting the URL. For example, if the user type/role is admin then the URL should be like the following:

http://localhost/Admin/Home/Index

If the user type/role is “Customer” then the URL should be like the following:

http://localhost/Customer/Home/Index

In such cases we need to make the routing for the various user types that our application supports. In this case the type of routing mapping will be like a combination of Static and Dynamic Segments. Also while maintaining a different routing mapping we need to be assured of the ordering of the map entries because they are applied in the order that they are added to the RouteTableCollection. This entry I'll add before the existing route map entry. The following is the entry for that.
  1. routes.MapRoute(  
  2.           name: "adminPrefix",  
  3.           url: "Admin/{controller}/{action}",  
  4.           defaults: new { controller = "Home"action = "Index" }  
  5.           );  
  6.   
  7.           routes.MapRoute(  
  8.               name: "dynamicRoute",  
  9.               url: "{controller}/{action}/{id}",  
  10.               defaults: new { controller = "Home"action = "Index"id = UrlParameter.Optional }  
  11.           );  
NOTE 
  1. Here I just changed the name of the old map entry to “dynamicRoute” instead of “Default”.
  2. The added map entry can map to those URLs that has “Admin” as the first segment then “Controller” as the second segment and finally the “Action” as the third segment. We've also specified the default values to the Controller and the Action method. If any value is not ed to the controller and the action then the default values will be used.
  3. Also you can define the parameters like ID or some other as per your needs in this route (in other words adminPrefix route).

II. URL Forming a Contract With the User's

Suppose that your MVC website (or rather it could be an ASP.NET website also, for this we assume that you did URL Rewriting in ASP.NET) is being used in the market for a long time and the URL of your application has formed a contract with the user's. Because most users have a habit of bookmarking the URL that they visit often and now a days even the latest browsers store a tag of your website that the user has normally visited through that browser. In such cases let's say if you try to rename your controller name from “EmployeeController” to “EHomeController”, then in such cases your user's bookmarks will not work and it could have a bad impact on the user's opinion about the functionality of the website. So for reducing such types of issues we can make an entry in the route mapping. The following could be the entry.

  • For demonstrating this we'll rename our “EmployeeController” to “EHomeController”
  • Add the following entry in the RouteConfig.cs file as the first entry in the RouteConfig.cs file.
    1. routes.MapRoute(  
    2.      name: "OldURL",  
    3.      url: "Employee/{action}/{id}",  
    4.      defaults: new { controller = "EHome"action = "Index"id = UrlParameter.Optional });  

NOTE: In the preceding entry, in the URL we've statically defined the controller name as “Employee” and dynamically specified the Controller segment value as “EHome” (the value that should be treated at runtime for EmployeeController) that is basically the renewed name of our Controller. Just try the application by entering /Employee/Index in the browser's URL, you'll get the result without any error, even though you've changed the Controller name from “EmployeeController” to “EHomeController” in your code.



III. Defining parameters in the routeConfig map entry and accessing them in the Controller

For demonstrating this let's understand a scenario, you are working on a project where you've a Employeecontroller on whose index view you are trying to load all the details of all Employees. If the browser's URL is:

http://localhost/Employee/Index

In other words with no arguments.

And let's say if the user es an argument then in that case you need to load only those employees whose criteria match the argument ed. In such case your browser's URL could be:

http://localhost/Employee/Index/1

For supporting this routing we've already defined the URL in the previous case for EmployeeController. Now we just need to update the EmployeeController's Index action method.

  1. public class EHomeController : Controller  
  2. {  
  3.     public ActionResult Index(string id = "0")  
  4.     {  
  5.         ViewBag.Message = "Index Function Called From EmployeeController With Id value as " + id;  
  6.         return View("_ViewResult");  
  7.     }  
  8. }  
NOTE:  
  1. Since MVC works on a naming convention then in this case the name of the parameter for the index method should be the same as what we defined in the RouteConfig.cs file map entry for the “Employee” controller. Else it will not any value to the id parameter.
  2. We can also load the value for the ID parameter by using RouteData.Values[“id”]. Which contains all the values that are in the route. We just need to take the precautions for the name we ed in the Values indexes.

IV. Prioritizing the Controller Namespace

Suppose you are working on a project, where you are using third-party controllers or you are even having a controller with the same name but in different namespaces or under different projects. In such cases, the MVC would not be able to resolve the errors amongst the various controllers and it will straight away give you an error. For demonstrating this we could have the following example.

  1. I'm adding a new folder to the project with the name “AdminController”
  2. A new class with the name “HomeController” that inherits from the Controller class inside the “AdminController” folder.

The following is the code snippet for that.

  1. namespace UrlRoutingDemo.AdminControllers  
  2. {  
  3.     public class HomeController : Controller  
  4.     {  
  5.         public ActionResult Index(string id = "0")  
  6.         {  
  7.             ViewBag.Message = "Index Function Called From AdminControllers(HomeController) With Id value as " + id;  
  8.             return View("_ViewResult");  
  9.         }  
  10.     }  
  11. }  
Now just try to run the application. You'll encounter the following error message:



We can resolve the following error by prioritizing the namespaces in the route map entry that is the fourth parameter of your MapRoute function.
  1. routes.MapRoute(  
  2.             "adminpriority",  
  3.             "Special/{controller}/{action}/{id}",  
  4.             new { controller = "Home", action = "Index", id = UrlParameter.Optional },  
  5.             new[] { "UrlRoutingDemo.AdminControllers" });  
NOTE: 
  1. The last parameter, the array of strings, tells the MVC to search for the controller first inside “URLRoutingDemo.AdminController”.
  2. Specifying the namespace next to each other in the fourth parameter doesn't tell the MVC to prioritize on the namespace on the basis of their order in the array, it will treat all the namespaces with equal priority if you define multiple namespaces in the fourth parameter.

    Now if you run the application you'll have the index view displayed from the “UrlRoutingDemo.AdminController” instead of the other namespace “UrlRoutingDemo.Controllers”. Because it was the first entry in the route map and we told MVC to search the Controller first in the “UrlRoutingDemo.AdminController” namespace than in others. And since it found the entry for the “HomeController” inside our “UrlRoutingDemo.AdminController” namespace that's why it is displayed in the view from there. Here is the output for that.

  3. We need to prefix the word “Special” to call the “UrlRoutingDemo.AdminController”, because that forms the first segment in the route map entry for “adminpriority”.
  4. While running the application modify the browser URL to /Special/Home/ and you'll get the following output.



    Let's try to update the browser's URL to:

    Employee/Index/

    You again get an error message stating page/view not found. This happened because inside the namespace “UrlRoutingDemo.AdminController”, MVC is not getting any controller with the name “Employee”. To resolve this issue we will try to add a new entry below the AdminController route entry, that will point to another controller residing in the other namespace “UrlRoutingDemo.Controllers” because our “EmployeeController” resides in the “urlRoutingDemo.Controllers” namespace.
    1. routes.MapRoute(  
    2.          "normalpriority",  
    3.          "General/{controller}/{action}/{id}",  
    4.          new { controller = "Home", action = "Index", id = UrlParameter.Optional },  
    5.          new[] { "UrlRoutingDemo.Controllers" });  
    Now this entry tells the MVC that if the controller is not found then the “UrlRoutingDemo.AdminController” namespace must be searched in the other namespace, “UrlRoutingDemo.Controllers”. You just need to prefix the segment with “General”.


V. Handling variable length of parameters

Until now we've defined routes having fixed numbers of segments. Now we need to define a route map entry supporting a variable amount of segments. In such cases we need to write a map entry like the following, that can handle a variable number of segments. For this I'll modify our “UrlRoutingDemo.AdminController” route map entry like the following:

  1. routes.MapRoute(  
  2.              "adminpriority",  
  3.              "Special/{controller}/{action}/{id}/{*otherseg}",  
  4.              new { controller = "Home", action = "Index", id = UrlParameter.Optional },  
  5.              new[] { "UrlRoutingDemo.AdminControllers" });  


From the preceding diagram you can see that our first segment is “Special”, then the controller name is “Home”. The action name is “Index” and the id segment is “4” and the other seg value is “Other/Segment”..

VI. Constraining Routes using Regular Expressions

We can also constrain the route using a regular expression, by providing a specific pattern or by restricting the route to a specific HTTP type (it could be Get or Post). For demonstrating this we'll again update the “UrlRoutingDemo.AdminController” route map entry.
  1. routes.MapRoute(  
  2.              "adminpriority",  
  3.              "Special/{controller}/{action}/{id}/{*otherseg}",  
  4.              new { controller = "Home", action = "Index", id = UrlParameter.Optional },  
  5.              new { controller = "^H*$" },  
  6.              new[] { "UrlRoutingDemo.AdminControllers" });  
Now this route can map only the controllers with names starting with “H”. We can also restrict the action method with the same type of regular expression. If you do so then that map entry would only be applicable to those controllers and the action whose name matches the pattern specified using a regex.