Cshtml files are compiled by Razor into C# files. To track down some errors (or just to understand Razor) it might be useful to examine the C# code generated.
For testing I created a small Razor view in an MVC4 project.
@{ string someString = "somestring"; var someBool = false; //- } <h1>Header</h1> Some other text and @someString. @if(someBool) { <p>Yeps!</p> } else { <p>Nope!</p> } |
The easiest way to view the generated C# code is by introducing a compile error to bring up the compilation error page. I uncommented the – on line 4 (which obviously is incorrect C# code). In the bottom of the error page, there is a link to display the compilation source.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | #pragma checksum "c:\temp\MvcApplication1\Views\Home\Index.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "F5D4970B00EF8FDAA849DEADF3F83414B9E573C1" //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. // Runtime Version:4.0.30319.18052 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ namespace ASP { using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Web; using System.Web.Helpers; using System.Web.Security; using System.Web.UI; using System.Web.WebPages; using System.Web.Mvc; using System.Web.Mvc.Ajax; using System.Web.Mvc.Html; using System.Web.Optimization; using System.Web.Routing; public class _Page_Views_Home_Index_cshtml : System.Web.Mvc.WebViewPage<dynamic> { #line hidden public _Page_Views_Home_Index_cshtml() { } protected ASP.global_asax ApplicationInstance { get { return ((ASP.global_asax)(Context.ApplicationInstance)); } } public override void Execute() { #line 1 "c:\temp\MvcApplication1\Views\Home\Index.cshtml" string someString = "somestring"; var someBool = false; - #line default #line hidden BeginContext("~/Views/Home/Index.cshtml", 78, 43, true); WriteLiteral("\r\n\r\n<h1>Header</h1>\r\n\r\nSome other text and "); EndContext("~/Views/Home/Index.cshtml", 78, 43, true); BeginContext("~/Views/Home/Index.cshtml", 122, 10, false); #line 9 "c:\temp\MvcApplication1\Views\Home\Index.cshtml" Write(someString); #line default #line hidden EndContext("~/Views/Home/Index.cshtml", 122, 10, false); BeginContext("~/Views/Home/Index.cshtml", 132, 5, true); WriteLiteral(".\r\n\r\n"); EndContext("~/Views/Home/Index.cshtml", 132, 5, true); #line 11 "c:\temp\MvcApplication1\Views\Home\Index.cshtml" if(someBool) { #line default #line hidden BeginContext("~/Views/Home/Index.cshtml", 155, 18, true); WriteLiteral(" <p>Yeps!</p>\r\n"); EndContext("~/Views/Home/Index.cshtml", 155, 18, true); #line 14 "c:\temp\MvcApplication1\Views\Home\Index.cshtml" } else { #line default #line hidden BeginContext("~/Views/Home/Index.cshtml", 185, 18, true); WriteLiteral(" <p>Nope!</p>\r\n"); EndContext("~/Views/Home/Index.cshtml", 185, 18, true); #line 18 "c:\temp\MvcApplication1\Views\Home\Index.cshtml" } #line default #line hidden } } } |
The interesting stuff starts at line 30, where a class is declared. Each razor view is compiled into an own class which derives from System.Web.Mvc.WebViewPage<ModelType>
. In this case I didn’t use @model
to specify the type of the model, so it is interpreted as dynamic
but for strongly typed views, this is where the model type ends up.
The contents of the view file are located inside the Execute()
method starting at line 43. Anything that is identified as C# code by the Razor compiler is copied as is into this function (including the incorrect – at line 49 that I used to trigger the compilation error page).
Everything that is not C# code is output to the resulting page by calls to WriteLiteral
. An embedded expression (@someString
) is output with the Write
. The difference between them is that Write
html encodes the output, while WriteLiteral
doesn’t.
BeginContext and EndContext
The call to WriteLiteral
on line 56 is surrounded by calls to BeginContext
and EndContext
. The documentation is not exactly clear on what they are for:
This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
When the documentation doesn’t provide any insight, there’s only one thing to do:
Use the source Luke!
Fortunately MVC is open sourced so there is not even any need use IL Spy to reverse compile the code, the actual code is directly available. Unfortunately the MVC code is written to be very flexible with regards to unit testing, which makes it somewhat hard to read.
After a few layers of indirection and depency injection and (obfuscation?) through the use of a dynamic
type it looks like the BeginContext
call will result in calling the BeginContext
method on any attached PageExecutionListener
which resides in the System.Web.Instrumentation
name space. It is built in support for instrumentation and profiling.
That might be handy, but on the other hand – if a razor view has performance problems in the actual view rendering something else is broken: the view should not contain any logic that can take considerable time. That kind of logic should go into the view model.
Unfortunately the MVC code is written to be very flexible with regards to unit testing. =)