Generating HTML emails with RazorEngine - Part 04 - Taking a step back: behind the scenes of Razor and RazorEngine
This is the fourth part of a 10-part blog series. You'll find a list of all the posts in this series in the introductory post. Make sure to review the Before we start section in the introductory post.
All the source code is on GitHub. Comment? Bug? Open an issue on GitHub.
Before diving any deeper into RazorEngine, it's worth spending a few minutes getting a good grasp of what Razor and RazorEngine really are and of how they work under the hood. It will save a lot of time and frustration when trying to customize or understand RazorEngine's behaviour.
This is a very quick and deliberately simplified overview of Razor.
What is Razor?
Razor is a language that lets you create document templates mixing static markup and code. Typically, the static markup is HTML and the code is C# or VB.NET. But it doesn't have to be. Razor is (sort of) language agnostic.
In practice, some of the rules that Razor uses to differentiate between static markup and code mean that it's better suited for templates where the markup is XML-like. And the Razor parser currently provided by Microsoft only supports parsing templates that use either C# or VB.NET. Nothing prevents your from extending it to support additional languages of course. But in practice, Razor is definitely best suited for document template that use either XML or HTML together with either C# or VB.NET.
The Razor syntax is very simple. In fact, it's essentially comprised of a single character: @
(sometimes accompanied with braces ()
or curly braces {}
to delimitate respectively expressions that contain spaces or blocks of code).
In particular, it's important to realize that in the expressions you commonly use in your ASP.NET MVC Razor views such as @Html
, @Url
or @Model
, the only part that Razor understands is the @
prefix. This tells the Razor parser that what follows is C# (or VB.NET) code. The Html
, Url
or Model
parts on the other side aren't something that Razor understands (or care about).
The Razor parser is implemented in the System.Web.Razor.Parser.RazorParser
class, which is part of the System.Web.Razor
assembly. Although it was created to support the new Razor view engine introduced in ASP.NET MVC 3, it's not tied to the ASP.NET MVC framework. It can be used by any application to parse Razor template files, including console apps or Windows Services.
This is what the RazorEngine library relies on to provide its Razor-parsing abilities.
How is a Razor template transformed into the final HTML document?
The process of parsing and executing a Razor template to generate the final HTML document is deceptively simple.
- The Razor parser parses your template and generate the source code of a class that contains an
Execute()
method that, when run, generates the final HTML document. - That class is then compiled on-the-fly into its own assembly using the compilation services provided in the
System.CodeDom.Compiler
namespace. - Once compiled, the assembly is loaded into the app domain and the class is instantiated.
- The class'
Execute()
method can now be called to generate the final HTML document.
But an example is worth a thousand words.
Seeing the Razor parser in action
In the accompanying source code on GitHub, you'll find a small console app in the Part 04
folder that shows the Razor parser in action.
This application parses the following Razor template:
@model ConsoleApplication.Models.WelcomeModel
@{
Layout = "MyLayout";
}
<h2>
Hi @Model.UserName,
</h2>
<div>
Welcome here
</div>
@{
var winner = new Random().Next() == 42;
}
@if (winner)
{
<div>
Congratulations! You're a winner. Choose your gift:
@foreach (var gift in Model.Gifts)
{
<p>
@Html.ActionLink(gift.Description, "Details", "Gifts", new {giftId = gift.Id})
</p>
}
</div>
}
It first parses it using the Razor view engine that the ASP.NET MVC framework uses to render Razor views. It then parses it again, this time using the RazorEngine library.
If you run this app and look at the source code generated by the ASP.NET MVC Razor View Engine, you'll find the following generated class:
public class _Page_ : System.Web.Mvc.WebViewPage<ConsoleApplication.Models.WelcomeModel>
{
protected System.Web.HttpApplication ApplicationInstance
{
get
{
return ((System.Web.HttpApplication)(Context.ApplicationInstance));
}
}
public override void Execute()
{
Layout = "MyLayout";
WriteLiteral("\r\n\r\n<h2>\r\n Hi ");
Write(Model.UserName);
WriteLiteral(",\r\n</h2>\r\n<div>\r\n Welcome here\r\n</div>\r\n\r\n");
var winner = new Random().Next() == 42;
WriteLiteral("\r\n\r\n");
if (winner)
{
WriteLiteral(" <div>\r\n Congratulations! You\'re a winner. Choose your gift:\r\n");
foreach (var gift in Model.Gifts)
{
WriteLiteral(" <p>\r\n");
WriteLiteral(" ");
Write(Html.ActionLink(gift.Description, "Details", "Gifts", new { giftId = gift.Id }));
WriteLiteral("\r\n </p>\r\n");
}
WriteLiteral(" </div>\r\n");
}
}
}
As you can see, there's neither magic nor any clever tricks here. The Razor parser generated the source code of a class deriving from System.Web.Mvc.WebViewPage<T>
(which declares an abstract Execute()
method via its WebPageExecutingBase
base class) and implemented the Execute()
method in the simplest possible way by just progressing linearly through the template file and:
- Writing any static markup (HTML in this case) verbatim to the output stream
- Inserting any code block or expression (C# in this case) verbatim into the body of the
Execute()
method of the generated class.
How does the Razor parser know whether it's reading static markup or code? Thanks to the @
character. Whenever it encounters @
, it knows that what follows is code that should be inserted verbatim in the body of the Execute()
method of the generated class. Whenever it encounters a space or a brace / curly brace signaling the end of a code expression / block, it switches back to static markup mode.
There are of course quite a few more twists involved but this is the gist of it.
When the ASP.NET pipeline reaches the point when the response body needs to be written to the output stream, it simply calls the Execute()
method of this generated class, which writes the final HTML document.
It's clear when seeing what the Razor parser actually does that the Html
, Url
or Model
expressions in your Razor views have nothing to do with Razor. They're simply properties of the WebViewPage
base class that the generated class derives from.
How RazorEngine differs from the Razor view engine used by ASP.NET MVC
The RazorEngine library parses Razor templates using the same RazorParser
class that ASP.NET MVC uses. With one big twist.
The console app for Part 04 in the accompanying source code parses the Razor template above using both the ASP.NET Razor view engine and the RazorEngine library.
If you run the app and look at the source code generated by the RazorEngine libray, you'll find:
public class RazorViewGeneratedByRazorEngine : RazorEngine.Templating.TemplateBase<><ConsoleApplication.Models.WelcomeModel> {
public override void Execute() {
Layout = "MyLayout";
WriteLiteral("\r\n\r\n<h2>\r\n Hi ");
Write(Model.UserName);
WriteLiteral(",\r\n</h2>\r\n<div>\r\n Welcome here\r\n</div>\r\n\r\n");
var winner = new Random().Next() == 42;
WriteLiteral("\r\n\r\n");
if (winner)
{
WriteLiteral(" <div>\r\n Congratulations! You\'re a winner. Choose your gift:\r\n");
foreach (var gift in Model.Gifts)
{
WriteLiteral(" <p>\r\n");
WriteLiteral(" ");
Write(Html.ActionLink(gift.Description, "Details", "Gifts", new {giftId = gift.Id}));
WriteLiteral("\r\n </p>\r\n");
}
WriteLiteral(" </div>\r\n");
}
}
}
As expected, the generated code is almost identical to that generated by the ASP.NET Razor view engine. The only notable difference is that the generated class derives from RazorEngine.Templating.TemplateBase<T>
instead of System.Web.Mvc.WebViewPage<T>
.
TemplateBase<T>
is a custom base class that's part of the RazorEngine library. The RazorEngine library configures RazorParser
to use this custom base class when generating code.
And this is the cause of most of the confusion, frustration and runtime exceptions that inevitably arise when using the RazorEngine library to parse Razor templates.
If you compare the interface exposed by TemplateBase<T>
and WebViewPage<T>
, you'll see that they're quite similar. For example, they both expose Layout
and Model
properties as well as RenderBody()
and RenderSection()
methods.
So using @Layout
, @Model
, @RenderBody()
or @RenderSection()
in your Razor template will work just fine when parsed with the RazorEngine library.
But try to use @Html
(for example) in a template parsed with RazorEngine and your application will blow up at runtime with:
RazorEngine.Templating.TemplateCompilationException: Unable to compile template. The name 'Html' does not exist in the current context
The reason for this should now be obvious: Html
isn't a part of Razor but is a property of the WebViewPage<T>
base class that ASP.NET MVC uses for its Razor views. Since the RazorEngine libary specifies its own base class TemplateBase<T>
for the generated code and since this base class doesn't expose a property named Html
, compilation of the generated class fails.
Why TemplateBase<T>
So why did the developer of the RazorEngine library choose to implement his own base class for the generated template classes instead of just using the WebViewPage<T>
base class that the ASP.NET MVC Razor view engine uses?
Simply because WebViewPage<T>
is heavily tied to the ASP.NET MVC framework and assumes that it's used within the context of a web request. You can explore its source code to see it by yourself.
Since the RazorEngine library is meant to be a general-purpose Razor parsing library that can be used within any application, including console applications or Windows Services, using WebViewPage<T>
wasn't an option.
Hence the creation of TemplateBase<T>
that implements many of the functionalities of WebViewPage<T>
but leaves out web-specific functionalities, such as WebViewPage's Html
and Url
properties.
In practice, it means that most of the Razor templates you would write to use as views in your ASP.NET MVC applications can't be parsed with the RazorEngine library as they most likely use properties or methods of the WebViewPage<T>
class that don't exist in RazorEngine's TemplateBase<T>
.
When writing Razor templates that will get parsed with RazorEngine, make sure to restrict yourself to the properties and methods exposed by TemplateBase<T>
.
Thankfully, the RazorEngine library lets you easily specify another base class to use for the generated code. So if you really want to be able to use @Html
or @Url
in your Razor templates, you can do it with not too much work. We'll see a first example of this in the next post in this series.
Further reading
If you'd like to learn more about how Razor works behind the scenes, look no further than Andrew Nurses's blog. Andrew is the main developer of the ASP.NET Razor view engine and has written a series of excellent articles on Razor:
- inside razor - part 1 - recursive ping-pong
- inside razor - part 2 - expressions
- using the razor parser outside of asp.net
- inside razor - part 3 - templates
- hosting razor outside of asp.net (revised for mvc3 rc)
- what's new in razor v2
- what else is new in razor v2?
- look ma! no gac! razor intellisense without gacing