Hosting Razor outside of ASP.Net (Revised for MVC3 RC!)

We recently released the latest preview release of MVC 3, including an updated version of Razor.  One of the things we did in this release is simplify the hosting APIs dramatically.  I did a demo of these new APIs in a pre-recorded PDC10 talk I did with Scott Hunter, which is available to stream here: http://bit.ly/ac7B0P.  As I promised in that talk (and a few others before and after it Confused smile), I'm finally blogging about the sample I showed in that talk!

The hosting APIs center around two classes:

  • RazorTemplateEngine: the primary entry point to the Razor engine
  • RazorEngineHost: defines the environment in which the generated Razor code will run

First, let's look at the Host.  RazorEngineHost is a class with a number of useful properties on it that can tweak the way the generated code behaves.  More interestingly (though also more complicated and a topic for later), it can be subclassed and used to swap out the parser and code generator with your own custom code to add new keywords and features to Razor for your scenario!  In the sample I showed at PDC, we just used the properties to configure things, like so:

// Set up the hosting environment

// a. Use the C# language (you could detect this based on the file extension if you want to)
RazorEngineHost host = new RazorEngineHost(new CSharpRazorCodeLanguage());

// b. Set the base class
host.DefaultBaseClass = typeof(TemplateBase).FullName;

// c. Set the output namespace and type name
host.DefaultNamespace = "RazorOutput";
host.DefaultClassName = "Template";

// d. Add default imports
host.NamespaceImports.Add("System");
host.NamespaceImports.Add("System.IO");

This code is just doing fairly simple (but powerful) stuff.  It first sets the base class for the template.  Since we are running Razor outside of ASP.Net, you don't have to use our base classes.  In fact, you have to provide your own!  The sample contains a simple base class which just writes the template content to a StringBuilder we can retrieve later.

Razor generated code using a certain convention (that can be overridden if necessary).  The generated template will contain a public method called "Execute" and that method will contain the users code and calls to one of the following methods:

  • Write() - When a user types "@foo", we generate Write(foo) in the generated code
  • WriteLiteral() - When a user types literal markup, like "<p>Foo</p>", we generate WriteLiteral("<p>Foo</p>") in the generated code

The names of these methods can be configured using the GeneratedClassContext property of the host, but we won't go into details on that here.  If you want to enable features like Sections, Templates and Helpers in your custom Razor host, you need to set some properties here but we'll cover that another time.

The host also defines the namespace and class name of the generated C# or VB code, and the list of namespaces to be imported into the template's generated code file.  Once we've configured our host, we construct a RazorTemplateEngine and give it this host to use:

// Create the template engine using this host
return new RazorTemplateEngine(host);

From there, we're set to generate some code!  Let's look at what happens when you click the "Load Template" button in the sample.

// Generate code for the template
GeneratorResults razorResult = null;
using (TextReader rdr = new StringReader(templateTextBox.Text))
{
    razorResult = _engine.GenerateCode(rdr);
}

Now that we have our engine configured, all we need to do is take the template source code (from a text box in this case), open up a TextReader for it, and pass it in to the RazorTemplateEngine we constructed earlier.  The result of that call is a structure called GeneratorResults which contains the following properties:

  • Success - A boolean indicating if parsing and code generation were successful
  • GeneratedCode - A CodeDOM tree representing the code that the Razor engine generated
  • ParserErrors - A list of parser errors, if any
  • Document - The root node of the Razor parse tree (we'll cover that later)

From this one call, you get all that data!  You can party on the parse tree, or use the CodeDOM APIs in the core .Net Framework to generate source code.  In the sample, I take the CodeDOM tree, compile an assembly using it, then load the assembly and the generated template type.  It takes quite a bit of code, so I'll leave that to the sample rather than posting it all here, but it's easily wrapped up in a helper method if you are going to be doing a lot of compiling!

It is important to note that Razor is not an interpreted language.  It actually follows the same compilation model as ASPX, despite the drastically different syntax.  The first time a user requests a Razor page, we generate the C#/VB code for it and compile it into an assembly.  All future requests, until the original file is changed, go against that compiled C#/VB code.  This means we can't easily do things like implementing "eval" because we aren't actually running the Razor code directly, we're just using it to generate C#/VB code.

Hopefully that gives you a quick summary of how to get your feet wet hosting Razor outside of ASP.Net.

Also, in MVC RC, we released Visual Studio support for Razor.  It's still an early release, so there might be some issues.  We'd like to hear from you if you encounter any issues!  Email [email protected] with your issues (related to VS tooling only please).  If you have other questions about Razor in general, tweet me at @anurse or email me at andrew AT andrewnurse DOT net!

Here's the sample: HtmlGen.zip (65.04 KB) (NOTE: MVC 3 RC is required).