A Web Developer's Look at the MVC4 Request Pipeline

This article is intended to give you a solid understanding of how client requests move through the Asp.Net Mvc 4 Pipeline.  As developers, we need to know what options we have to override default behaviour, modify or augment requests as they arrive (and as they’re executed) and extend the pipeline as our requirements dictate.

A friend recently asked why his constructor was being called multiple times on the “same” request.  I thought the best way illustrate why this happens would be to explain the entire request stack.  Or say, “Just because!”, but I also enjoy writing articles like this, so we’ll go with the entire stack version.

If you’re looking for the “Coles notes” version, scroll down about halfway to see the summary. Smile

Once Upon a Browser

I’ll skip a few steps (like keyboard interrupts on the client device) and skip to the part where the HTTP request enters the interesting bits of the MVC Framework.

On the first request, application startup is executed from Global.asax, which configures the following:

  • Routes for WebAPI
  • Global filters
  • General routing
  • Bundling and minification
  • Authorization providers

Bundling and minification relate to client-required resources such as scripts and stylesheets and don’t affect our core request.  Authorization providers – for FaceBook, Twitter, Microsoft Account and Google Account – are now supported out-of-box, but don’t relate directly to our method execution either.

Assuming the app is already running, we can get back to the incoming request.

The registered route handler (an MvcRouteHandler by default) won’t find a physical file for our controller action, so instead it associates a matching route in the RouteCollection (which we configured above) and gets the IHttpHandler that will be used for the request.  In our case, it’s an MvcHandler, and it’s instantiated with the current instance of the RequestContext.

There are some other pipeline events that are firing, true, but now, we’re moving into the interesting bits as a web dev (versus someone developing HTTP handlers).  The MvcHandler is now processing the request.  If you haven’t modified the default behaviour, a DefaultControllerFactory is spun up and creates the controller.

As we ultimately inherit all our controllers from System.Web.Mvc.Controller we have a few interesting events around the time our action (a public, non-overloaded, non-static class method) is being executed that we can participate in.

  • OnActionExecuting
  • OnActionExecuted
  • OnAuthorization
  • OnException
  • OnResultExecuting
  • OnResultExecuted

However, we also have the ability to create our own filters, which is arguably a better way to interact with the request.

So, filter execution then kicks in, in this order:

  • Authorization
  • Action
  • Result
  • Exception

Note that the final order of execution is tied to the following orders of precedence:

  • Global filters
  • Controller filters
  • Action filters
  • The order in which those were applied (within their type)

Our method execution and view rendering are wrapped up in there as well.  You can see this by inheriting from ActionFilterAttribute and overriding the Action and Result events.  Put this attribute on your action and you’d see something like this if you were Debug.WriteLining:

  • OnActionExecuting
  • Action (method) executing
  • OnActionExecuted
  • OnResultExecuting
  • View executing
  • OnResultExecuted

One of the great features of the MVC Framework is model binding, which is part of this request pipeline but really deserves it’s own article.  Model binding is the process in which incoming HTTP data - such as form data or query string parameters - are converted to .Net types.  This means that we don’t need to cast a form field to an int, but more interestingly, that the form collection (or querystring parameters) as a whole can be converted to complex .Net types like a Person object. (I have some samples on this here.)

There’s one last point worth mentioning here. When the view is being executed it is done so by the registered view engine(s).  You can replace this with anything available in the community or you can create your own.  There are a couple of exceptions to this part of execution:

  • You are not returning a result that derives from ViewResult, in which case your method is executed and the result is returned to the client. You can set your response types (like 403, or 200) explicitly.
  • Your action is an EmptyResult, or it returns void which is translated into an empty result. Again, you can set your response types as you wish.
  • Your action generates an exception, in which case the last piece of your code being executed may just very well be your custom exception handler.

What About the Coles Notes?

A shorthand version of the following might be as follows:

  • Incoming request
  • App initialization (on the first request to the application)
  • Routing magic/kung fu
  • Controller creation
  • Filter execution
  • Action invocation
  • Filter execution
  • View Rendering
  • Final filter execution

And BAM! Your user sees something in the browser.

Riddle Me This

Now, a quick quiz.  Given the following view:

    <h2>Execution Count Testing</h2>
        The execution count from the view is: 
        @Model. (@MvcApplication3.MvcApplication.ConstructorCallCount 
        constructor calls.)
        Rendered from the partial view via Html.RenderAction, the 
        execution count is: @{Html.RenderAction("ExecutionCountAsPartial");}.
        Rendered via ajax request from the partial view, the execution 
        count is: <span id="count-result"></span>

    @section scripts
    <script type="text/javascript">
        $(function () {
            var requestUrl = '@Url.Action("ExecutionCountAsPartialViaAjax")';
            .success(function (data) {

…and the following controller code:

    private int _myCounter = 0;

    public HomeController()

    public ActionResult ExecutionCount()
        return View(++_myCounter);

    public PartialViewResult ExecutionCountAsPartial()
        return PartialView(++_myCounter);

    [OutputCache(NoStore=true, Duration=1)]
    public PartialViewResult ExecutionCountAsPartialViaAjax()
        return PartialView("ExecutionCountAsPartial", ++_myCounter);

…would you have thought you would have seen this?


Back to our original question, we have to differentiate between what a client request is and what you might refer to as an execution request.  In this case, the view for ExecutionCount is invoking three “execution” requests across two “client” requests.  The first two action invocations are made through the first client request, and when that is returned to the browser, the client is making a second request via Ajax which results in the third method execution.

So, two requests from the client and three actions executed, and the constructor is called three times.  We’re trackin’.  So, why all the controller constructor calls?

Well, let’s turn this around a second.  What if different actions are attributed with different filters?  Authorization requirements?  What about out-of-order execution (as in, single page applications making requests on whatever timing plays out)? The result in any of those would be a design where we’re no longer able to DoOneThing and we’d have to write composite filters that were able to handle the permutations of the above and other scenarios.  The MVC pipeline is designed such that each request passes through the above steps, regardless of where the request comes from.  This ensures consistency in execution for all requests.

If you are looking for different behaviour you can work around this if you simply separate your concerns: what I’m saying here is that a controller shouldn’t be used to do heavy lifting.  My answer to my friend was simply that his constructor was likely doing too much work and, in his case, user state shouldn’t be managed in the constructor.

More Reading

Now, all that is likely enough for most of us, but if you want to dive deeper you can read up on the following topics in more details:

As well, code from this article is available in the following repository on GitHub: