Skip to content

John Bennett's blog

The life and times of an ASP.NET MVC Framework request

Monday, February 16, 2009

[I recently wrote this up for some folks at work, and thought it was worth re-posting here.]

In this post I’ll detail what happens during a request to the default ASP.NET MVC site that you get when you create a new MVC project in Visual Studio.  My goal is to list the major classes involved and how they interact.  There is a lot more that goes on during a request, and I’ve completely skipped action filters.  This is just the highlights.

First, there are some steps that happen at application start:

  • The web.config file adds System.Web.Routing.UrlRoutingModule.  During initialization, UrlRoutingModule attaches event handlers for HttpApplication’s PostResolveRequestCache and PostMapRequestHandler events.  These handlers do some magic (using pre-existing extension points in ASP.NET) to ensure that requests in both IIS6 and IIS7, and for various versions of .NET, all wind up in the right place.
  • The web.config file also adds MvcHttpHandler.  Requests matching *.mvc are handled with it.  This is also part of the goo that makes MVC work with different versions of IIS and .NET.
  • Application_Start().  This HttpApplication event handler is overridden in the Global class (Global.asax.cs).  It calls the RegisterRoutes() method, also defined on the Global class.
    • RegisterRoutes() informs the routing infrastructure which requests to route to which controllers and actions, how to populate action method parameters from the URL, and what the default parameter values are.  It does this by adding Route objects to an ordered collection.

The application is now setup to begin accepting requests.  Here are pseudocode and descriptions for what happens on each request.  Note that wherever you see “httpContext”, the object is actually derived from the HttpContextBase class defined in System.Web.Abstractions.  This avoids the web forms dependency on HttpContext, and allows for code execution and testing outside the context of a web request.

This could use a sequence diagram, and I hope to create one soon.  In the meantime, you can try to follow along in Reflector (or the MVC source if you’ve downloaded it).

  • The UrlRoutingModule event handlers do their magic, the result of which is the creation of an MvcHttpHandler object and a call to its ProcessRequest() method.  MvcHttpHandler derives from UrlRoutingHandler, which is part of the routing API and implements IHttpHandler.
  • mvcHttpHandler.ProcessRequest():
    • routeData = RouteCollection.GetRouteData(httpContext);
      • In turn, calls route.GetRouteData() on each route in the collection. The collection is whatever we added during RegisterRoutes() at application startup, in order. The first route to return a non-null value wins the request.  RouteData contains the names of the controller and action (both required), along with the values of any other tokens defined in the route URL format.
        • RouteData is important, as it carries request-specific data (e.g., URL token values) that are not present in HttpContext.  The RequestContext object is an aggregation of HttpContext and RouteData, and is available to all later processing of the request.
    • mvcRouteHandler = routeData.RouteHandler; (MvcRouteHandler implements IRouteHandler, which is part of the routing API.  MvcRouteHandler is just a very thin adapter between routing and Mvc.)
    • mvcHandler = mvcRouteHandler.GetHttpHandler();
    • mvcHandler.ProcessRequest(httpContext);  (MvcHandler implements IHttpHandler.)
      • Gets the controller name from the RouteData.
      • Gets the singleton instance of DefaultControllerFactory.
      • controller = factory.CreateController(requestContext, controllerName);
      • controller.Execute(requestContext);
        • Creates a ControllerContext, which is just the RequestContext plus a reference to the controller instance.
        • Gets the action name from the RouteData.
        • Creates an ActionInvoker
        • actionInvoker.InvokeAction(controllerContext, actionName)
          • Maps values in the RouteData to parameters on the action method with matching names
          • If there are complex parameter types that can’t be directly mapped from RouteData values, the invoker instantiates an IModelBinder for each parameter and calls BindModel() on it to get the parameter value.
          • Invokes the action method with all the parameters.
            • In typical usage, action methods get data, add that data to the ViewData dictionary and/or the Model property. Then the action method calls the controller’s View() method.
            • controller.View() method:
              • Gives each registered view engine an opportunity to “claim” the view. The default WebFormViewEngine uses the virtual path to locate the view. The default virtual paths it checks are:
                • ~/views/{controller}/{action}.aspx and .ascx
                • ~/views/shared/{action}.aspx and .ascx
              • View() returns a ViewResult object (derived from ActionResult) containing a reference to the view engine and the virtual path to the view.
            • The action method typically returns the ViewResult it gets from View().
          • The invoker calls result.ExecuteResult():
            • When called on a ViewResult, this method asks the view to render itself:
            • view.Render(outputWriter)
              • Performs the rendering and writes the output directly to the outputWriter (which is the HTTP response stream).