Stuff

Razor Page Views behind the curtain

Did you ever wonder, how the cshtml code that you write in your Razor Pages looks like after you build your site? I did, and I dug a little.

When you build your project, MSBUILD magic seems to be looking for all the .cshtml files and feeds them into the Roslyn compiler.[1]

In the old days, those compiled pages were dumped in a subdirectory within the obj directory. Recent versions of ASP.NET Core seem to bake them into the resulting binaries, which is why the generated files don’t get emitted to the filesystem anymore. But luckily, there’s a EmitCompilerGeneratedFiles flag which you can set in your csproj file to make those files visible again. I got this in the official docs recently.

When having a minimal Razor Page like this:

@page

hi from index!

You then get something like to following. Keep in mind, I removed a ton of compiler generated attributes and simplified stuff.

internal sealed class Pages_Index : Page
{
public async override Task ExecuteAsync()
{
WriteLiteral("hi from index!\n");
}

// [… other properties removed for brevity]

public ViewDataDictionary<Pages_Index> ViewData => PageContext?.ViewData;
public Pages_Index Model => ViewData.Model;
}

So basically, Roslyn creates a Page class that inherits from PageBase. This is where properties like HttpContext come in.

One step up in the inheritance ladder, theres RazorPageBase. Here (among other stuff) we get our ViewBag property populated, which was there all along, just buried in the ViewContext.

Finally, we end at the IRazorPage interface. Aside from the Layout property and more internals, the ExecuteAsync() method gets declared here. This method gets called by the framework when the page is visited.

When having a Razor page with more markup, this method looks like code in the old PHP days but is just optimized stuff that humans are not supposed to work with. It’s filled with a mix method calls, variable assignments and many WriteLiteral("markup stuff\n"); calls.

WriteLiteral literally writes that string into the output buffer later.

Tag helpers

Within the mud of generated code, you’ll see some bunched up TagHelper methods. This is how they get transformed from handy HTML tags with extra attributes to the resulting C# code.

They get discovered by the compiler and after some code later, the ProcessAsync method of the helpers gets called. The resulting output then gets written to the same output buffer like the rest.

For me, the most interesting thing after all is this: when you’re using MVC, its views actually get compiled to the exact same Page classes like above.

The only difference to a generated Razor page file is this attribute at the top (again, I cut some namespace declaration for brevity):

- [assembly: RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Pages_Index), @"mvc.1.0.razor-page", @"/Pages/Index.cshtml")]
+ [assembly: RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Index), @"mvc.1.0.view", @"/Views/Index.cshtml")]

Don’t get this wrong: there is a huge amount of additional (and different) framework code involved for MVC and Razor Pages.

After all, knowing how this looks under the hood also made the difference between those two code blocks apparent:

@{
var foo = "bar";
}


@functions {
// C# members (fields, properties, and methods)
}

Coincidentally, examples displaying its generated result were added to the docs.


Footnotes

  1. I haven’t found the relevant source code within the Roslyn repository, yet. ↩︎