Understanding Routing in ASP.NET MVC

Posted by: Sumit Maitra , on 6/8/2012, in Category ASP.NET MVC
Views: 406699
Abstract: ASP.NET routing was initially launched as MVC routing to support clean URL semantics for sites built on ASP.NET MVC. However, it was later pushed up in the framework to become a part of ASP.NET core and thus available for WebForms as well. In this article, we look at how routes work and how you can use them in your MVC web application to expose functionality in a transparent and discoverable way.

Before ASP.NET MVC, URLs in web application mapped to physical files at a disk location. So for example if you had a URL ‘hxxp://www.testurl.com/products/Default.aspx’ it simply meant there was a Default.aspx file in a ‘products’ folder at the root of the website. This URL had no other meaning. Any parameters it took was probably passed in the query string making it look like ‘hxxp://www.testurl.com/products/Default.aspx?productid=99’

Note: To avoid hyperlinks in this article, we have renamed http to hxxp. Wherever you see hxxp, please read it as http.

However if we built a MVC site for the same, the URL would probably look like ‘hxxp://www.testurl.com/product/’ and ‘hxxp://www.testurl.com/product/details/99’. By convention (and default) the first URL maps to the ProductController class with an action named Index. The second URL maps to the ProductController class with and action named Details. As we can see, MVC Routing helps abstract URLs away from physical files which by default maps to Controller/Action method pairs by default.

We will now go into details of how this mapping happens and how to modify the defaults. But before that, some best practices!

 

Routing and URL Etiquettes

The concept of clean URLs essentially came down from frameworks like Ruby. Some of the accepted conventions for clean URLs are:

1. Keep URLs clean: For example instead of ‘hxxp://www.testurl.com/products/Default.aspx?productid=99&view=details’ have ‘hxxp://www.testurl.com/product/details/99’

2. Keep URLs discoverable by end-users: Having URL parameters baked into routes makes URLs easier to understand and encourages users to play around and discover available functionality. For example in the above URL ‘hxxp://www.testurl.com/product/details/100’ would mean the product details for product id 100. But guessing numbers is no fun as we will see in the next practice.

3. Avoid Database IDs in URL: In the above examples we have used 100, which is a database id and has no meaning for the end user. But if it were to use ‘hxxp://www.testurl.com/product/details/whole-milk’ our users would probably try out ‘hxxp://www.testurl.com/product/details/butter’ on their own. This make points 1 and 2 even more relevant now.

However if IDs are unavoidable consider adding extra information in the URL like ‘hxxp://www.testurl.com/product/details/dairy-product-99/’

Clean URLs described above have the added advantage of being more ‘search robot’ friendly and thus is good for SEO.

With the - what (is routing) and why (we should have clean URLs) out of the way, let’s looking deeper into how routing works in MVC.

How does the Route Handler work

Quite sometime back I saw a Scott Hanselman presentation on MVC2 where he stopped the execution of sample MVC app on the Controller Action and then walked through the stack trace to show the inner workings of the MVC pipeline. I tried the same for the route handler and ended up with a 1000+ pixels stack trace, part of which is reproduced below. As highlighted below, the first thing that happens in the pipeline during transition from System.Web to System.Web.Mvc is the execution of all registered handlers.

stack-trace

Fact is the RouteHandler is first to be executed. It follows these steps (not evident from the stack trace)

1. Check if route is static file on disk, if so the resource is served directly

2. If it’s not a static route, check if there is a custom route handler, if so it hands off the request to the custom route handler

3. If there are no custom route handlers it hands over to the default MVC Routing handler. Now that we have reached the route handler let us see how it treats routes

To see the entire pipeline refer to Steve Sanderson’s MVC Pipeline diagram here.

Understanding the default Route declaration

In ASP.NET MVC, by default a couple of routes are defined for you. With the introduction of WebAPI, another additional route is declared for WebAPI controller actions. Let us look at these routes and see what they imply.

default-routes

The Route labeled (1) is named ‘Default’ and it has a url template of type {controller}/{action}/{id}. Note the third parameter, which is an anonymous object with three properties, each of which matches a section in the url template.

The Route labeled (2) is information for MVC Routing to ignore anything that ends with an axd and having additional parameters. The {*pathinfo} is a wildcard for all query params.

Finally the Route Labeled (3) is for WebApi based controllers that derive of ApiController instead of the regular MvcController. Note WebApi routes are adding using the MapHttpRoute method.  Also the route adds an ‘api/’ for every controller present. So even though you may have the ValuesController derive from ApiController and in the same folder as the HomeController, it will still be mapped to hxxp://<root>/api/Values. Here ‘api’ is the static part of a route.

Next we will see how we can add our own custom routes for MvcControllers.

Creating Custom Routes

Now if a user visited ‘hxxp://www.testurl.com/product/edit/1’ it would match the ‘Default’ route defined above and MVC would look for a controller called ProductController with an action Edit that takes an input parameter called id.

I have created a small application with a single Entity called Product that has the following properties

product-entity

This entity is serialized to the database using the EntityFramework and I built a Controller using the Visual Studio ‘Add Controller’ wizard. The files added are as highlighted below:

files-added

Now if we look in the ProductController.cs, we will find Action methods for Get and Post Http actions for each of the above views. This makes the following default routes available

image

Now let us see some Sample data

sample-data

As we can see above, we have two categories of products and 3 different names. Now each are identified by their IDs and you can use the Edit/Details/Delete action links to act upon them. However, if this list were to get much bigger, it would be really nice to have the ability to look at all items under the category ‘Electronics’ or ‘Electronics’ from ‘Sony’. Currently there is no such ‘filter’ available.

Let us implement this. First we add a parameter category to the Index action method, and filter the data we are getting from the Database and return it to the view.

index-by-category

Now if we run the application and provide the URL

http://localhost:11618/Product/Index/Electronics

We will see the Index page but nothing has changed. It is still showing all Products.

index-filter-not-working

This is because the default route has specified a parameter called {id} but no {id} parameter was available in the Controller method. If we put a breakpoint in the Controller method, we will see that the ‘category’ parameter is coming in as null.

category-parameter-null

How can we fix this?
..By defining a new Route that tells the routing handler how to navigate to an action method, when a ‘category’ parameter is specified for the Index method. The route is follows

product-category-route

As we can see, we have defined a new route that expects a category parameter. Now when we provide Electronics as a parameter, we get a nicely filtered list

index-category-filter-working

Now we could add this URL to the Category column such that clicking on any category would filter the list.

Next, to continue making our URL more discoverable, we see we can add a Name filter too. Let us see what the Route and code looks like

route-with-category-and-name-filter

The change is simple, we have added {name} in the URL template and then specified it as a parameter in the anonymous object.

Next we updated the action method in the controller as follows

action-method-with-category-and-name

We basically do a bit-wise operation to determine which of the parameters have been passed. 00 – No parameters, 01 – Name passed, 10 – Category Passed, 11 – Both name and category passed.

Of the four cases case 1: is interesting because as we see, we can mix clean urls with urls using named query strings. This is because the sequence of parameters is important. So we cannot mix up the sequence of parameters in URL and expect MVC to understand it. If there are optional parameters then parameters coming after the optional param must be named in the Url for the routing to work correctly.

So we have now seen what the default ASP.NET route means and how we can leverage routes in MVC to make discoverable URLs that respond to changes in the URL by presenting context aware data.

To round off, we see how we can use the Routing mechanism to generate URLs for us as I mentioned above.

Using MVC Routing to Generate URLs

Currently our Index page only shows links for Edit, Details and Delete. However we could convert the Category and Name column to have links that route MVC to pull up the index page with the selected category and name. In the Index.cshtml, we change the Razor markup as shown below to generate the link

 

action-link-generation-using-routes

ActionLink and RouteLink

ActionLinks use the context of the current page while generating the target link. This results in the route-mapping happening based on the sequence in which the routes have been added. However if want to use a specific route to generate the URL we can use the RouteLink HTML helper. An example is shown below

route-link-generation-using-routes

Here we are trying to setup the ‘Index’ heading into a link that navigates to the Product/Index page without any parameters. If we use Action Link, it will use the current page context and automatically add the available parameters, which will result in generated link always pointing to the current page. Instead if we use RouteLink and ask the routing framework to use the ‘Default’ route, then the link that is generated will point to Product/Index always. Thus the RouteLink html helper comes in really handy when we have to pick and choose which of the available routes we want to use to generate URLs.

Conclusion

With this, we come to a close of this post. We looked at the basics of MVC routing and understood what the default routing means. We also took a look at a few examples of how routing helps make URLs discoverable and how parameters can be passed using clean URLs.

To conclude, MVCs routing implementation frees up URLs from physical association to a file thus opening up a LOT of flexibility with how URLs can be presented and interpreted in an MVC application.

Remember Routing is not a ‘MVC only’ feature, we just covered how it works in an MVC application. More on ASP.NET and WebForms routing to come.

The entire source code of this article is on GitHub can be downloaded over here. Download as a zip file

See a Live Demo

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Sumit is a .NET consultant and has been working on Microsoft Technologies since his college days. He edits, he codes and he manages content when at work. C# is his first love, but he is often seen flirting with Java and Objective C. You can follow him on twitter at @sumitkm or email him at sumitkm [at] gmail


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Gerrie on Tuesday, June 19, 2012 8:43 PM
one of the best introduction article about routing
Comment posted by Sumit on Friday, June 22, 2012 4:13 AM
Thanks Gerrie, glad you enjoyed it!
Comment posted by Kartik on Friday, July 13, 2012 7:12 AM
Sumit Sir after reading 5 different books in vain I read this article. The routing concept is very clear to me now and i am hoping to read some more concept articles from you.
Comment posted by Sumit on Wednesday, July 18, 2012 1:38 AM
Thanks Kartik, glad the article helped. Feel free to suggest what would help you. You can reach me on twitter @sumitkm
Comment posted by Siji Sajin on Thursday, September 6, 2012 6:29 AM
Articls on DotNet curry are best .

I am trying to find Introduction to MVC in your site but unable to find. Could you help me out.

We have lot of Sites discussing MVC but i am totally confused of how to start.
Comment posted by Suprotim Agarwal on Thursday, September 6, 2012 11:50 PM
Siji, Thanks for the kinds words.

Does this help?

http://www.dotnetcurry.com/ShowArticle.aspx?ID=370
Comment posted by Nirmal subedi on Sunday, September 23, 2012 12:57 PM
nice article...
Comment posted by Nikhil on Monday, October 8, 2012 3:51 PM
So far the best in MVC Routing! Thanks Sumit
Comment posted by Aaryan Abhishek on Wednesday, November 14, 2012 3:43 AM
Nice article. But I would like to add one more info. The new 'MapRoute' which we add should precede the default existing MapRoute. otherwise it is not considered at all.
Comment posted by Tom on Monday, December 3, 2012 7:29 AM
COnstructive Comments:
3rd paragraph needs a little more elaboration for the novice (i.e., the association between product and ProductController, and that there is an "Index" method by default. And, also, what is 99 in the second URL.
Comment posted by Sumit on Wednesday, December 12, 2012 6:24 AM
Hello Tom,
Thanks for you inputs. I'll try and update the post, till then here is a little more information.

MVC by default, looks for a controller based on the URL. For example in the above example after the domain the first part of the URL says /product/ so MVC will look for a controller called ProductController so that's your relationship between Product and ProductController. The next part of the URL is usually mapped by MVC to the Action method in the Controller. So /Product/Create or /Product/Index points to the Action Method Index or Create in the controller. However Index is setup as default so even if you say http://localhost:1234/Product/ (and ignore the Index part) it call the Index method on the Product action controller.

As far as the 99 is concerned it's just a sample ProductId I used.

Hope this Helps.
Comment posted by Ashok on Wednesday, December 12, 2012 11:04 PM
This is a great article which has very well explained the working of routes.
Comment posted by SandeepSingh on Tuesday, December 18, 2012 4:18 AM
In mvc3 with Razor Framework in Action method how can we pass default parameter for only when page is load.
Comment posted by Ganesh Gude on Tuesday, December 18, 2012 4:22 AM
In Mvc3 how to  hit the controller using jquery dialog box
Comment posted by Bhargava on Thursday, December 27, 2012 3:57 AM
nice article
Comment posted by Ramesh on Thursday, January 31, 2013 1:16 PM
Thanks for this article. It helped me to understand MVC routing.
Comment posted by Husain Abbas on Tuesday, March 5, 2013 9:42 AM
This Article is one of the best i read abt routing. thx
Comment posted by Vaibhav on Thursday, March 14, 2013 2:20 AM
Very nice
Comment posted by while on Wednesday, May 15, 2013 11:24 AM
You can put all the filters in Entity framework Linq query instead of doing complicated bitwise logic (what if there was a third or fourth parameter?)
Pseudocode:
where filter == null || product.WhateverField == filter
and filter2 == null || product.WhateverField2 == filter2
and ...
Comment posted by Swetha on Monday, June 24, 2013 2:04 AM
Nice Post
Comment posted by Norrin Radd on Sunday, July 7, 2013 3:05 PM
Great Article! Now I have a better grasp on routing! Thanks
Comment posted by sobanieca on Wednesday, July 17, 2013 6:46 AM
Hello, I have created a package that automatically maps routes basing on the actions definition that you have entered. No need to manually create .MapRoute() definitions. The whole project site is here: https://github.com/sobanieca/AutoRouting

Regards,
Adam Sobaniec
Comment posted by Julian Rosebury on Tuesday, August 6, 2013 10:49 AM
Well explained, thank you
Comment posted by Samyukta on Thursday, August 15, 2013 5:56 AM
Great post.
Comment posted by Mario Berthely on Wednesday, August 28, 2013 4:38 PM
I would like to copy your code to my test projects. Unfortunately they are in images. Thanks
Comment posted by Benny D. on Tuesday, November 19, 2013 3:55 PM
One of the best introductions to MVC routing I've come across. Thank you!
Comment posted by vijay on Thursday, February 27, 2014 3:19 AM
very well explained..
Comment posted by l on Tuesday, July 15, 2014 5:22 AM
i
Comment posted by Amit Singh on Wednesday, July 23, 2014 11:14 PM
Really I am impressed and is too good article
Comment posted by passion on Monday, March 23, 2015 4:06 AM
really nice and detail description;

for more you can see the below links

http://www.getmscode.com/2015/03/mvc-routing.html