With the new version of MVC (MVC 3; which has just delivered the Beta), the guys over in Redmond have been preparing a new View Engine for the incredible MVC platform. Razor is a new presentation layer framework that replaces the WebForms view engine in your MVC site. With Razor, we can create nice clean html without the overhead of all those WebForms based tags and processes.
Now, there have been quite a few blog posts about Razor already, so it’s worth going for a google (or bing!) and see what you can find. What you might find though, is a particularly interesting blog post by BuildStarted entitled “Razor View Engine without MVC at all” whereby he had crafted a self contained Razor compiler that didn’t require MVC. Now there have been some changes since he did his original build, namely type changes in the now new System.Web.Razor.dll, but I’ll walk you through what I’ve changed, based on his original code.
Redifining our base types
In his original code, the types BaseWriter and BaseWriter were defined, which are good starting points, but I wanted to define them as interfaces, and rename them more appropriately (in my eyes) as ITemplate, etc. So, the redifined interfaces are as follows:
namespace RazorEngine
{
/// /// A razor template. ///
public interface ITemplate
{
#region Properties
/// /// Gets the parsed result of the template. ///
string Result { get; }
#endregion
#region Methods
/// <summary>
/// Executes the template.
/// </summary>
void Execute();
/// <summary>
/// Writes the specified object to the template.
/// </summary>
/// <param name="object"></param>
void Write(object @object);
/// <summary>
/// Writes a literal to the template.
/// </summary>
/// <param name="literal"></param>
void WriteLiteral(string literal);
#endregion
}
/// <summary>
/// A razor template with a model.
/// </summary>
/// <typeparam name="TModel">The model type</typeparam>
public interface ITemplate<TModel> : ITemplate
{
#region Properties
/// <summary>
/// Gets or sets the model.
/// </summary>
TModel Model { get; set; }
#endregion
}
Code language: HTML, XML (xml)
}
The implementations are essentially exactly like his originals:
namespace RazorEngine
{
using System.Text;
/// <summary>
/// Provides a base implementation of a template.
/// </summary>
public abstract class TemplateBase : ITemplate
{
#region Fields
private readonly StringBuilder builder = new StringBuilder();
#endregion
#region Properties
/// <summary>
/// Gets the parsed result of the template.
/// </summary>
public string Result { get { return builder.ToString(); } }
#endregion
#region Methods
/// <summary>
/// Executes the template.
/// </summary>
public virtual void Execute() { }
/// <summary>
/// Writes the specified object to the template.
/// </summary>
/// <param name="object"></param>
public void Write(object @object)
{
if (@object == null)
return;
builder.Append(@object);
}
/// <summary>
/// Writes a literal to the template.
/// </summary>
/// <param name="literal"></param>
public void WriteLiteral(string literal)
{
if (literal == null)
return;
builder.Append(literal);
}
#endregion
}
/// <summary>
/// Provides a base implementation of a template.
/// </summary>
/// <typeparam name="TModel">The model type.</typeparam>
public abstract class TemplateBase<TModel> : TemplateBase, ITemplate<TModel>
{
#region Properties
/// <summary>
/// Gets or sets the model.
/// </summary>
public TModel Model { get; set; }
#endregion
}
Code language: HTML, XML (xml)
}
Refactoring compilation
The genius of the original code is the ability to generate a class on the fly that inherits from our base types, TemplateBase, and TemplateBase. I’ve refactored his code and have abstracted the creation of the language services away from the core part of compilation. The reason I’ve done this, is the Razor team have introduced support for VB syntax in Razor templates. So let’s look at the interface:
namespace RazorEngine
{
using System.CodeDom.Compiler;
using System.Web.Razor;
/// <summary>
/// Defines a provider used to create associated compiler types.
/// </summary>
public interface IRazorProvider
{
#region Methods
/// <summary>
/// Creates a code language service.
/// </summary>
/// <returns>Creates a language service.</returns>
RazorCodeLanguage CreateLanguageService();
/// <summary>
/// Creates a <see cref="CodeDomProvider"/>.
/// </summary>
/// <returns>The a code dom provider.</returns>
CodeDomProvider CreateCodeDomProvider();
#endregion
}
Code language: PHP (php)
}
And let’s define a provider for C#:
namespace RazorEngine
{
using System.CodeDom.Compiler;
using System.Web.Razor;
using Microsoft.CSharp;
/// <summary>
/// Provides a razor provider that supports the C# syntax.
/// </summary>
public class CSharpRazorProvider : IRazorProvider
{
#region Methods
/// <summary>
/// Creates a code language service.
/// </summary>
/// <returns>Creates a language service.</returns>
public RazorCodeLanguage CreateLanguageService()
{
return new CSharpRazorCodeLanguage();
}
/// <summary>
/// Creates a <see cref="CodeDomProvider"/>.
/// </summary>
/// <returns>The a code dom provider.</returns>
public CodeDomProvider CreateCodeDomProvider()
{
return new CSharpCodeProvider();
}
#endregion
}
Code language: HTML, XML (xml)
}
With this abstraction in place, we can easily create a new provider for the VB specific language services required by Razor.
Ok, let’s look at our compiler. The compiler is responsible for compiling our dynamic code. As he had mentioned previously, Razor generates a class that requires a Write, WriteLiteral and Execute metod. The Execute method it generates simply creates statements to call Write and WriterLiteral, which it does so by parsing the content of the template. There is a lot of refactored code here, so let’s have a look:
namespace RazorEngine
{
using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Razor;
using System.Web.Razor.Parser;
/// <summary>
/// Compiles razor templates.
/// </summary>
internal class RazorCompiler
{
#region Fields
private readonly IRazorProvider provider;
#endregion
#region Constructor
/// <summary>
/// Initialises a new instance of <see cref="RazorCompiler"/>.
/// </summary>
/// <param name="provider">The provider used to compile templates.</param>
public RazorCompiler(IRazorProvider provider)
{
if (provider == null)
throw new ArgumentNullException("provider");
this.provider = provider;
}
#endregion
#region Methods
/// <summary>
/// Compiles the template.
/// </summary>
/// <param name="className">The class name of the dynamic type.</param>
/// <param name="template">The template to compile.</param>
/// <param name="modelType">[Optional] The mode type.</param>
private CompilerResults Compile(string className, string template, Type modelType = null)
{
var languageService = provider.CreateLanguageService();
var codeDom = provider.CreateCodeDomProvider();
var host = new RazorEngineHost(languageService);
var generator = languageService.CreateCodeGenerator(className, "Razor.Dynamic", null, host);
var parser = new RazorParser(languageService.CreateCodeParser(), new HtmlMarkupParser());
Type baseType = (modelType == null)
? typeof(TemplateBase)
: typeof(TemplateBase<>).MakeGenericType(modelType);
generator.GeneratedClass.BaseTypes.Add(baseType);
using (var reader = new StreamReader(new MemoryStream(Encoding.ASCII.GetBytes(template))))
{
parser.Parse(reader, generator);
}
var builder = new StringBuilder();
using (var writer = new StringWriter(builder))
{
codeDom.GenerateCodeFromCompileUnit(generator.GeneratedCode, writer, new CodeGeneratorOptions());
}
var @params = new CompilerParameters();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
@params.ReferencedAssemblies.Add(assembly.Location);
}
@params.GenerateInMemory = true;
@params.IncludeDebugInformation = false;
@params.GenerateExecutable = false;
@params.CompilerOptions = "/target:library /optimize";
var result = codeDom.CompileAssemblyFromSource(@params, new[] { builder.ToString() });
return result;
}
/// <summary>
/// Creates a <see cref="ITemplate" /> from the specified template string.
/// </summary>
/// <param name="template">The template to compile.</param>
/// <param name="modelType">[Optional] The model type.</param>
/// <returns>An instance of <see cref="ITemplate"/>.</returns>
public ITemplate CreateTemplate(string template, Type modelType = null)
{
string className = Regex.Replace(Guid.NewGuid().ToString("N"), @"[^A-Za-z]*", "");
var result = Compile(className, template, modelType);
if (result.Errors != null && result.Errors.Count > 0)
throw new TemplateException(result.Errors);
ITemplate instance = (ITemplate)result.CompiledAssembly.CreateInstance("Razor.Dynamic." + className);
return instance;
}
#endregion
}
Code language: PHP (php)
}
The compiler takes in an instance of our IRazorProvider contract and it uses the provider instance to create the language service types it needs to dynamically create our template. When the GetTemplate method is called, internally it calls the Compile method which generates and compiles the code.
The compiler works as before, it generates a class which inherits from one of our base types, TemplateBase and TemplateBase, and compiles the assembly in memory. If all the code compiles correctly, we return our CompilerResults back to the GetTemplate method, which in turn will instantiate an instance of our type as an ITemplate. If there are any errors during the compile process, we throw a TemplateException allowing us to report the exact compile errors back to the user.
Parsing and caching templates
I’ve introduced a static, simply named Razor. It’s duty is to use the compiler to generate a template and execute it, passing in whatever model we need to. It also supports the ability to cache the template after it has been created, allowing us to reuse templates that we created previously.
One thing I had noted from his previous code, is that he was using Reflection to set properties on the template (BaseWriter) and execute the method. As our code lies in the same assembly as our contracts, we can simply cast the newly created instance of our template back to the contract type, and call properties on that.
Here is the full class:
namespace RazorEngine
{
using System;
using System.Collections.Generic;
/// <summary>
/// Process razor templates.
/// </summary>
public static class Razor
{
#region Fields
private static RazorCompiler Compiler;
private static readonly IDictionary<string, ITemplate> Templates;
#endregion
#region Constructor
/// <summary>
/// Statically initialises the <see cref="Razor"/> type.
/// </summary>
static Razor()
{
Compiler = new RazorCompiler(new CSharpRazorProvider());
Templates = new Dictionary<string, ITemplate>();
}
#endregion
#region Methods
/// <summary>
/// Gets an <see cref="ITemplate"/> for the specified template.
/// </summary>
/// <param name="template">The template to parse.</param>
/// <param name="modelType">The model to use in the template.</param>
/// <param name="name">[Optional] The name of the template.</param>
/// <returns></returns>
private static ITemplate GetTemplate(string template, Type modelType, string name = null)
{
if (!string.IsNullOrEmpty(name))
{
if (Templates.ContainsKey(name))
return Templates[name];
}
var instance = Compiler.CreateTemplate(template, modelType);
if (!string.IsNullOrEmpty(name))
{
if (!Templates.ContainsKey(name))
Templates.Add(name, instance);
}
return instance;
}
/// <summary>
/// Parses the specified template using the specified model.
/// </summary>
/// <typeparam name="T">The model type.</typeparam>
/// <param name="template">The template to parse.</param>
/// <param name="model">The model to use in the template.</param>
/// <param name="name">[Optional] A name for the template used for caching.</param>
/// <returns>The parsed template.</returns>
public static string Parse<T>(string template, T model, string name = null)
{
var instance = GetTemplate(template, typeof(T), name);
if (instance is ITemplate<T>)
((ITemplate<T>)instance).Model = model;
instance.Execute();
return instance.Result;
}
/// <summary>
/// Sets the razor provider used for compiling templates.
/// </summary>
/// <param name="provider">The razor provider.</param>
public static void SetRazorProvider(IRazorProvider provider)
{
if (provider == null)
throw new ArgumentNullException("provider");
Compiler = new RazorCompiler(provider);
}
#endregion
}
Code language: PHP (php)
}
With all this, we can now easily make calls to parse out templates:
string template = “Hello @Model.Name!”;
User user = new User { Name = “Matt” };
string result = Razor.Parse(template, user);
Notice how type inference allows us to call Razor.Parse without expressing the model type.
Find the project attached, and make sure you check out BuildStarted for more Razor goodness 🙂
Update…
I’ve added a VBRazorProvider to the project to allow you to process VB syntax in your templates, and I’ve also added a Clear method to the template to clear down the last result of a template before it is processed again (this is useful when you are caching templates, as the behaviour of TemplateBase types is to append to the internal StringBuilder.
Download Razor Project | Visit BuildStarted