ASP.NET MVC Deep Dive – Routing


Oftentimes as developers we use frameworks without really knowing how hte magic happens because we are so focussed on delivery and lean upon the frameworks. So for instance, have you ever wondered how the following are achieved:

  1. How do images, stylesheets and javascript files (resources in .NET parlance) get served?
  2. How does the framework “know” how to return a Http 404?

In order to answer these questions one has to first understand the ASP.NET pipeline and in particular the Routing aspect of it.

Effectively when our web server gets an incoming request, it gets routed to an appropriate http handler. According to the msdn page

An ASP.NET HTTP handler is the process that runs in response to a request that is made to an ASP.NET Web application

That, in a nutshell, answers both questions. In essence, in the ASP.NET framework we have:

  • one handler, ResourceHandler, which will serve resources
  • and we have another handler (in the case of MVC it’s the MvcHandler) that will return a Http 404. In fact, the MvcHandler is responsible for returning web pages in response to something a request like /Home/Account.

Now let’s get to the nitty gritty of how this is actually implemented in ASP.NET MVC.

Routing in ASP.NET

In the ASP.NET pipeline, an incoming request will be matched against a list of routes and based upon some route constraints, each request will be filtered down to the appropriate http handler. In this post we shall ignore the route constraints because this post does a great job at explaining it. However, what should be clear, and at the risk of repeating myself, is that based upon the incoming request, as soon as a route in the route list is matched, the response is returned by the corresponding http handler.

I shall first deal with how a request such to /Home/Index is handled in the context of ASP.NET MVC.

The default IRouteHandler and IHttpHandler in ASP.NET MVC

If you were to create an ASP.NET MVC4 Internet application and then look at the RouteConfig class, you would see something like:

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 MapRoute extension method in System.Web.Mvc is simply a convenience method for, unsurprisingly, mapping an incoming request to a route.

public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
    //Guard clauses

    Route route = new Route(url, new MvcRouteHandler())
    {
        Defaults = CreateRouteValueDictionary(defaults),
        Constraints = CreateRouteValueDictionary(constraints),
        DataTokens = new RouteValueDictionary()
    };

    if ((namespaces != null) && (namespaces.Length > 0))
    {
        route.DataTokens["Namespaces"] = namespaces;
    }

    routes.Add(name, route);

    return route;
}

What is of particular interest is the MvcRouteHandler class, which as it turns out it the default implementation for IRouteHandler in ASP.NET MVC. If you were to open up MvcRouteHandler, you would see:

public class MvcRouteHandler : IRouteHandler
{    
    ...
    protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
        return new MvcHandler(requestContext);
    }
    ...
}

The method GetHttpHandler has to be implemented since it is defined in IRouteHandler. In effect, in our implementation we are setting the IHttpHandler i.e. actual http handler to MvcHandler, which is hence the default implementation of IHttpHandler in ASP.NET MVC.

Before going any further let us summarise how an incoming request is eventually routed to the appropriate route handler.

  1. We first define the url pattern e.g. “{controller}/{action}/{id}” for a given route
  2. Each given route, and therefore url, is mapped onto a particular implementation of IRouteHandler e.g. Route route = new Route(url, new MvcRouteHandler()). In other words, if a given url matches a route, then use the given IRouteHandler.
  3. The IRouteHandler will then define what IHttpHandler to use.

Resource handler in ASP.NET

It should be clear that resources do not get processed by the MvcHandler since there are no matching controllers. Hence, I started looking for classes that implement IRouteHandler and found ResourceRouteHandler in System.Web.WebPages.ApplicationParts. In GetHttpHandler of that class, ResourceHandler is returned and it is effectively this implementation of IHttpHandler which is returning the various resources.

This also answers a question I had regarding how the “~” in paths was working.

The only thing that I couldn’t figure out is where the url to route mapping is defined although I looked in the various *.config files and IIS.

Http 404 in ASP.NET MVC

This is a bit trickier in the sense that there might be different versions. Let me explain by trying to go to

  • /NotFound
  • /Home/About/something/nonexistent

In the first instance, here’s the error page:

ControllerFactory404while the second instance, the error page is:

IIS404I had a hunch about why this was happening but as I couldn’t properly prove it, I decided to ask the question of why this was happening on Stackoverflow. Although the accepted answer also does not conclusively explain why, it did concur with my hypothesis (taking the scienfitic approach here) and until I get told otherwise, I’ll just accept it.

To summarise the answer given, in the first case it is ASP.NET MVC producing the error page while in the second case, it is IIS. In the next post we will delve deeper into the former case.

Advertisements
Posted in ASP.NET MVC

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: