Introduction
When I first started learning MVC one of the few concepts that I found new and unusual was routing. My concern was that why do we need it in the first place. I could understand that Models, Views and Controllers are an important part of any MVC application. After all user interaction is an important part of any application and that’s what the controller does, it handles user interaction and selects the appropriate view to render. User interacts with our application using the view or the UI. And as we know Model represents the application's data that is required in almost every application, after all every application has certain entities and relationships between the entities.
But when we think about routing, it doesn’t seem to address any of the concerns unlike the previous concepts. So let’s first try to understand using an example what routing is and then we will see the problem it solves and why it is such an important part of MVC application.
Background
Why use routing?
We are used to traditional web applications in which the URL the user enters in the browser maps to some specific physical file mapped to a directory on the web server which we call the virtual directory. That is why when we are developing an eCommerce application to sell products online we use URL's like :http://www.myproducts.com/electronics.aspx.
If we observe the above URL we can easily say that electronics.aspx is an ASP.NET page that should probably list the electronic products. So this is self explanatory. Now that we can display the electronic products to the user , we want to add a subcategory called mp3 that will display only the MP3 players to the user instead of all the electronic products. Simple we apply the filter on the page and when the user clicks the mp3 hyperlink on the page we pass a unique id in the URL that represents the mp3 player. So our problem is solved. Now our URL looks like http://www. myproducts.com/electronics.aspx?catId=8. So we have solved the problem efficiently ,after all why to display all the products when the user is interested to see only the mp3 players. But then we had not anticipated that we are having hundred’s of products to sell. So we end up with URL’s like
www.myproducts.com/electronics.aspx?catId=8
www.myproducts.com/electronics.aspx?catId=9
www.myproducts.com/electronics.aspx?catId=15
The above URL’s appears to be just perfect for us and syntactically they are, but thinking from the end user’s perspective which product URL you think he will remember more than the others. I could say none of the above URLs. We are after all humans and we are more likely to remember words than numbers. So all the above URLs are the same for the end user. Wouldn’t it be nice if we have URLs like:
www.myproducts.com/electronics/products/mp3
www.myproducts.com/electronics/products/tv
www.myproducts.com/electronics/products/laptop
The above URLs seem self explanatory and the user can easily guess that if we sell CDs he just have to append cd at the end of the common URL
www.myproducts.com/electronics/products/cd
These types of URL’s are called hackable URL’s and it is one of the advantages of using routing . Above example might have given you some idea about routing. Now we can say that "routing decouples the request
handler from the URL request" as in the example above.
Let’s go back to the original question ,why is routing important to an MVC application?. The answer is that Routing is so important in MVC because in MVC we don’t have physical files to handle the different requests unlike traditional web forms applications. Routing is a mechanism in MVC that decides which action method of a controller class to execute. Without routing there’s no way an action method can be mapped
to a request. Routing is a part of the MVC architecture so ASP.NET MVC supports routing by default. Let’s see how routing fits in an MVC application. Following diagram depicts the role of routing in the request processing pipeline.
At the application launch time the RegisterRoutes method is called in the Application_Start
eventhandler of the glabal.asax. There is a collection of routes called the RouteCollection
that contains all the registered routes in the application. RegisterRoutes
method registers the routes in this collection. A Route defines a URL pattern and a handler to use if the request matches the pattern. Unlike traditional web applications we do not have one to one mapping between the URL and the handler but rather have url patterns that matches to a group of URL’s. So a single pattern in the RouteCollection
can match multiple URLs and these matching URL's can be handled by separate handler’s. The following method represents the default method that add routes to the route table. In MVC 4.5 this is located in the RouteConfig.cs file in the App_Start folder. In MVC 4 routes are registered in the gloabal.asax.
The RegisterRoutes
method above is called in the "Application_Start
" eventhandler of the gloabal.asax, so routes are registered initially when the application is launched. The first parameter to the MapRoute
method is the name of the route ,in this case “Default”. The second parameter is the pattern to match the URL. Pattern consists of segments which can be literals or the placeholders inside the {} braces. Segments are separated using the slash "/ " character. In this case all the segments in the pattern are placeholders. We can think of a placeholder as the name of a variable and the value that is mapped to the placeholder segment in the URL is the value of the variable. The third parameter specifies the default values for the placeholders if they are not specified.
To better understand let’s see some URL patterns and how the routing engine breaks these into different segments.
URL | Segments |
Home/Index/3 | Controller=Home,Action=Index,Id=3 |
Home/Index | Controller=Home,Action=Index |
Home | Controller=Home,Action=Index |
In the above URL patterns if we don’t supply values for some of the segments the default values are used. So the URL Home(third row in the table above) is mapped to the Index method in the Home controller since the action method defaults to Index. It’s worth making one point, if the Index action method is defined with a non-nullable parameter requesting the URL Home would throw error since id parameter cannot be passed null value. So the below method will throw an exception.
public ActionResult Index(int id)
{
return View();
}
If you want the users to access the site using just the URL Home just make the id parameter as nullable.
public ActionResult Index(int? id)
{
return View();
}
By using routing in MVC application we have one another big advantage and that is using routing we can easily create search engine optimized URLs It is because when using routing we are not restricted to fixed URLs and can very easily re-factor the URLs so that they are easily discovered by the search engines. SEO doesn't include only URL optimization though URL optimization is part of SEO.
Using custom routes
The above example used the default route that is automatically defined when we create a new MVC application. But we are not restricted to use just that route ,we can register our own custom routes in the routing collection. So let’s go ahead and add some routes to the routing table. Suppose we are building an application to manage the details of the students in a college. We can define the route for it as:
routes.MapRoute(
name: "College",
url: "Student/{studentId}",
defaults: new { controller = "Student", action = "Details"}
);
In this route we have named our route as College and the controller that handles the request is Student and the action is Details. To invoke the Details action method we just need to prefix the controller name in the URL .The default value of action will be used which we have defined as Details here. But one important point ,"always place the more specific routes before the more general routes" since the order in which we register the routes is respected by the routing engine. So if we place the default route before the route that we have registered above our action method will never get called. Avoiding this mistake can save us a lot of debugging effort later on.
Route Constraints
As we have seen earlier we can use placeholders in the URL pattern of the route which defines the variable values. Also we can define the default values for the placeholders so these default values will be used if user doesn't provide any values for the placeholders in the URL As we define the default values for the placeholders we can also define the constraints to apply to the placeholder values. Suppose we have a placeholder called studentId and we have defined this studentId as an integer argument of an action method. So if the user enters the string value for this studentId parameter he will get an exception. We can use a route constraint in this case to limit the studentId value to integer values. The map route method has an overload which expects a constraint as an anonymous type.
We can define such a constraint as:
routes.MapRoute(
name: "College",
url: "Student/{studentId}",
defaults: new { controller = "Student", action = "Details"},
constraints:new{id=@"\d+"}
);
If a URL matches a route but the placeholder does not satisfy the constraint the route is not considered a valid match and the routing engine continues searching for the matching route. So using the constraints we can specify the validations on the placeholder values which would otherwise throw exception.
For other MVC concepts
Model Binding in MVC