Razor view engine outside of ASP.NET

There are many Razor view engine examples on web, but they all try to explain it with their ten ton of application around it. I wanted to strip all the custom code people have around it and see Razor in its bare minimum. Just a “Hello world” of Razor view engine implementation. If people want to explain me the word “float” throw a leaf in water and show me this is float, don’t build a million dollar boat on water to show me what is float.

So, lets get started. In its bare bone implementation, I have HTML fragment as such:

new private const string Template = @"
  <html>
   <head>
     <title>Welcome to Razor</title>
   </head>
   <body>
     <p>Dear @Model.Name</p>
     <p>Hope you are enjoying the weather in @Model.City.</p>
   </body>
</html>";

and a Model defined as such:

var Model = new
{
   Name = "Rafat Sarosh" ,
   City = "Seattle"
};

And here is the code which uses Razor and marries this template and Model to get back a HTML with data.

ICustomeTemplateEngine templateEngine = new CustomTemplateEngine();
string s = templateEngine.Execute(Template, Model);

That’s it. If you cut and paste rest of code provided and just change the template and model you are in business of using Razor view engine outside of ASP.NET. You really don’t need anything more than this.

But I am sure, you need to know more, so let’s talk about all the glue code we need to accomplish this two magical lines defined above.

We need a RazorEngineHost and a base class called “CustomTemplate” and a Template (which is your plain vanilla html, shown above), and using these three things we can create an assembly. This assembly takes the Model (Data) and gives back a HTML.

Because an assembly is created on the fly, and it is given a Model to work. We need to wrap our Model in a Dynamic object. The DynamicObject enables you to override operations like getting or setting a member, calling a method, or performing any binary, unary, or type conversion operation. Read more about it here.

Now let me show you some code fragment.
Create the Razor engine as follows:

RazorTemplateEngine CreateRazorEngine()
{
   var host = new RazorEngineHost(new CSharpRazorCodeLanguage())
  {
    DefaultBaseClass = typeof(CustomTemplate).FullName,
    DefaultNamespace = NAMESPACE
  };

  host.NamespaceImports.Add("System");
  host.NamespaceImports.Add("System.Collections");
  host.NamespaceImports.Add("System.Collections.Generic");
  host.NamespaceImports.Add("System.Dynamic");
  host.NamespaceImports.Add("System.Linq");
  return new RazorTemplateEngine(host);
}

CustomTemplate implements an interface called ICustomTemplate.


public interface ICustomTemplate    {
  string Name { get; set; }
  string Body { get; }
  void SetModel(dynamic model);
  void Execute();
}

Using the Engine and template, generate the code as follows:

System.Web.Razor.GeneratorResults templateCodeResults = razorEngine.GenerateCode(new template, “TemplateName”, NAMESPACE, “TemplateName.cs"); 

With the generated code create the assembly

//Magic happening here.
using (var codeProvider = new CSharpCodeProvider())
{
     var compilerParameter = new CompilerParameters(referencedAssemblies, assemblyName, false)
     {
       GenerateInMemory = true,
       CompilerOptions = "/optimize"
     };

     var compilerResults = codeProvider.CompileAssemblyFromDom(compilerParameter, templateCodeResults.GeneratedCode);

     if (compilerResults.Errors.HasErrors)
    {
      throw new InvalidOperationException(compileExceptionMessage);
    }

    return compilerResults.CompiledAssembly;
}

This creates an assembly which has just one method Execute as of ICustomTemplate. Here is the what actually happens behind the scene in execute method in the freshly minted assembly.

public override void Execute()
{
  this.WriteLiteral("<html> \r\n <head>\r\n <title>Welcome to Razor</title>\r\n</head>\r\n <body>\r\n<p>Dear ");
  this.Write(((dynamic) base.get_Model()).Name);
  this.WriteLiteral("</p>\r\n<p>Hope you are enjoying the weather in ");
  this.Write(((dynamic) base.get_Model()).City);
  this.WriteLiteral(".</p>\r\n</body>\r\n</html>");
}

As you can see the generated assembly has dynamic keyword to access the model properties. Model is passed to this assembly using the SetModel of ICustomeTemplate wrapped around a dynamic object. To see the wrapper dynamic object  check the code attach with this post.
Out of the generated assembly, create an instance and set the model and call execute method on it. That’s it.

Assembly assembly = GenerateAssembly(templates);
ICustomTemplate engine = (ICustomTemplate)Activator.CreateInstance(assembly.GetType(NAMESPACE + "." + TEMPLATE_NAME, true, false));
engine.SetModel(WrapModel(Model));
engine.Execute();
return engine.Body;

Here is the full code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Web.Razor;
using System.Reflection;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

namespace Razor
{
class LearningRazor
{
static void  Main(string[] args)
{
var Model = new
{
Name = "Rafat Sarosh" ,
City = "Seattle"
};
ICustomeTemplateEngine templateEngine = new CustomTemplateEngine();
string s = templateEngine.Execute(Template, Model);
}

new private const string Template = @"<html>
<head>
<title>Welcome to Razor</title>
</head>
<body>
<p>Dear @Model.Name</p>
<p>Hope you are enjoying the weather in @Model.City.</p>
</body>
</html>";

}

public interface ICustomTemplate
{
string Name { get; set; }
string Body { get; }
void SetModel(dynamic model);
void Execute();
}

public abstract class CustomTemplate : ICustomTemplate
{
private readonly StringBuilder buffer;

[DebuggerStepThrough]
protected CustomTemplate()
{
buffer = new StringBuilder();
}

public string Name { get; set; }

public string Body
{
get { return buffer.ToString(); }
}

protected dynamic Model { get; private set; }

public void SetModel(dynamic model)
{
Model = model;
}

public abstract void Execute();

public virtual void Write(object value)
{
WriteLiteral(value);
}

public virtual void WriteLiteral(object value)
{
buffer.Append(value);
}
}

public interface ICustomeTemplateEngine
{
string Execute( string template, object model = null);
}

public class CustomTemplateEngine : ICustomeTemplateEngine
{
private RazorTemplateEngine razorEngine;
private const string NAMESPACE = "Razor";
private const string TEMPLATE_NAME = "CustomTemplate";

public string Execute(string template, object Model = null)
{
KeyValuePair<string, string> templates =  new KeyValuePair<string, string>(TEMPLATE_NAME, template) ;
Assembly assembly = GenerateAssembly(templates);

ICustomTemplate engine = (ICustomTemplate)Activator.CreateInstance(assembly.GetType(NAMESPACE + "." + TEMPLATE_NAME, true, false));
engine.SetModel(WrapModel(Model));
engine.Execute();

return engine.Body;
}

RazorTemplateEngine CreateRazorEngine()
{
var host = new RazorEngineHost(new CSharpRazorCodeLanguage())
{
DefaultBaseClass = typeof(CustomTemplate).FullName,
DefaultNamespace = NAMESPACE
};

host.NamespaceImports.Add("System");
host.NamespaceImports.Add("System.Collections");
host.NamespaceImports.Add("System.Collections.Generic");
host.NamespaceImports.Add("System.Dynamic");
host.NamespaceImports.Add("System.Linq");

return new RazorTemplateEngine(host);
}

private static readonly string[] referencedAssemblies = BuildReferenceList().ToArray();

protected virtual Assembly GenerateAssembly(KeyValuePair<string, string> kv)
{
if (razorEngine == null)
razorEngine = CreateRazorEngine();

var assemblyName = NAMESPACE + "." + Guid.NewGuid().ToString("N") + ".dll";

System.Web.Razor.GeneratorResults templateCodeResults = razorEngine.GenerateCode(new StringReader(kv.Value), kv.Key, NAMESPACE, kv.Key + ".cs");

if (templateCodeResults.ParserErrors.Any())
{
var parseExceptionMessage = string.Join(Environment.NewLine + Environment.NewLine, templateCodeResults.ParserErrors);
throw new InvalidOperationException(parseExceptionMessage);
}

//Magic happening here.
using (var codeProvider = new CSharpCodeProvider())
{
var compilerParameter = new CompilerParameters(referencedAssemblies, assemblyName, false)
{
GenerateInMemory = false,
OutputAssembly = @"C:\temp\RazorAssembly.exe",
CompilerOptions = "/optimize"
};

var compilerResults = codeProvider.CompileAssemblyFromDom(compilerParameter, templateCodeResults.GeneratedCode);

if (compilerResults.Errors.HasErrors)
{
var compileExceptionMessage = string.Join(Environment.NewLine + Environment.NewLine, compilerResults.Errors.OfType<CompilerError>().Where(ce => !ce.IsWarning).Select(e => e.FileName + ":" + Environment.NewLine + e.ErrorText).ToArray());

throw new InvalidOperationException(compileExceptionMessage);
}

return compilerResults.CompiledAssembly;
}
}

private static IEnumerable<string> BuildReferenceList()
{
string currentAssemblyLocation = typeof(CustomTemplateEngine).Assembly.CodeBase.Replace("<a href="file:///">file:///</a>", string.Empty).Replace("/", "\\");

return new List<string>
{
"mscorlib.dll",
"system.dll",
"system.core.dll",
"microsoft.csharp.dll",
currentAssemblyLocation
};
}

protected virtual dynamic WrapModel(object model)
{
if (model == null)
{
return null;
}

if (model is IDynamicMetaObjectProvider)
{
return model;
}

var x = model.GetType()
.GetProperties();
var propertyMap = model.GetType()
.GetProperties()
.Where(property => property.CanRead && property.GetIndexParameters().Length == 0)
.ToDictionary(property => property.Name, property => property.GetValue(model, null));

return new MyTemplateModelWrapper(propertyMap);
}

}

public class MyTemplateModelWrapper : DynamicObject
{
private readonly IDictionary<string, object> propertyMap;

public MyTemplateModelWrapper(IDictionary<string, object> propertyMap)
{

this.propertyMap = propertyMap;
}

public override IEnumerable<string> GetDynamicMemberNames()
{
return propertyMap.Keys;
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;

return binder != null && propertyMap.TryGetValue(binder.Name, out result);
}

public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (binder != null)
{
propertyMap[binder.Name] = value;

return true;
}

return false;
}
}
}

Thanks to Kazi Manzur for his explanation of Razor. His article and code helped me understand Razor implementation outside of ASP.NET.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s