Thursday 16 December 2010

Dive deep into ASP.NET MVC Razor view rendering process

Introduction
These few days I spent some time studied the inner rendering process of the new Razor view engine for ASP.NET MVC 3. I would like to share my finds with you here.

Background
ASP.NET MVC executing starts with MvcHandler.ProcessRequest() and it follows the following path if the controller is returning a ViewResult,

RazorView.RenderView() View rendering starts from ViewEngine specific view's RenderView() method. For Razor ViewEngine, that is RazorView's RenderView(). The following is the code.

It first casts the instance object to WebViewPage. The instance object is an instance of class compiled from our view code. Then it sets some properties, looks for StartPage (if it is a normal view, the RunViewStartPages is true, if it is a partial view, RunViewStartPages is false) and calls webViewPage.ExecutePageHierarchy(). ExecutePageHierarchy is defined in System.Web.WebPages.WebPageBase class and is not overridden by any sub classes. And there are two versions, one is virtual and without any parameter, while the other one is non-virtual and with a bunch of parameters.

Non Virtual WebPageBase.ExecutePageHierarchy()
RenderView() is calling the non-virtual one. The following is the code,


It first calls PushContext to save current context information, then checks whether there is a start page (defined by _ViewStart.cshtml file). If yes, then calls start page's ExecutePageHierarchy(), otherwise calls the ExecutePageHierarchy() virtual function. And finally calls PopContext() to clean up.

PushContext() and Preparation for Rendering


  1. It firstly saves parameter "writer" to _currentWriter and also the context information. The parameter "writer" is HttpContext.Response.Output object from ViewResultBase.ExecuteResult()
  2. It then creates a TextWriter (_tempWriter) object and pushes it to the OutputStack.
  3. It then pushes a new Dictionary object to SectionWritersStack
  4. It then assigns the BodyAction to _body variable if it is not null

We need to pay particular attention to the step 2 & 3. OutputStack is defined in PageContext, it is a stack of TextWriter


If you check Write() and WriteLiteral() methods defined in WebPageBase, you will notice that they all write to Output object which is the top object from OutputStack. So the purpose of the step2 is to write all contents to _tempWriter, not to the "writer" parameter. This is because if the view has a Layout, it needs to "fit" the content into Layout.

Next, SectionWritersStack is also defined in PageContext, and it is a stack of Dictionary of SectionWriter,


In Razor View Engine, you can create different sections in Layout page, for example,

@DefineSection("header" required:false)

And in corresponding view,

@section header {
This is content for section header
}

Razor view rendering process starts from the View first, then the Layout. SectionWritersStack is used to track sections and their corresponding content defined in View. For the example above, the SectionWriterStack will contain a dictionary object with one element (key = "header" and the corresponding content = "
This is content for section header
").

ExecutePageHierarchy() virtual function and View rendering


This function is very simple. If you ignore the first bit relating to falcon debugging, it is only a call to Execute(). Now is the fun part, Execute() is defined in System.Web.WebPages.WebPageExecutingBase class, but it is never overridden in MVC framework, in fact, it is written by us (developers), that is the view file (cshtml). ASP.NET runtime will compile our view code into a class. If you go to C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\ folder, you will be able to see code generated by ASP.NET runtime.

PopContext() and Layout Rendering
Ok, so it executed our code, next is the PopContext().


  1. It first reads the content from _tempWriter, when executing Execute() method, our View writes to _tempWriter.
  2. Then it checks whether the View has a Layout
  3. If yes, it pushes _currentWriter to OutputStack. So all Layout content will be rendered to _currentWriter. And it then calls RenderSurronding() to render the Layout, one of its parameter is the a delegate to write current View's content to a TextWriter. Layout rendering process is exact same as View's rendering process. View file and Layout file are inheriting from the same base class System.Web.UI.WebPageBase<T>.
  4. If no, it simply writes the View's content to _currentWriter.
  5. It calls VerifyRenderedBodyOrSections() to make sure
    1. RenderBody() is called
    2. If a section is required in Layout (@RenderSection("sectionName", required:true)), it makes sure that there is a corresponding section defined in View (@section SectionName { })
    3. If a section is defined by view(@section sectionName { }), it makes sure that the section name is rendered in Layout.
    If any of the above criteria is not met, it will throw an Exception.
  6. Balance SectionWritersStack and WebPageContextStack.

Summary
To recap, the following is the method executing sequence