For a special requirement at work (actually a mix with self requirement) I need to get a chunk of HTML content from a persistence-system. Taken as is, it does not appear a big challenge but analyzing the problem a little bit more deeper I saw that that the HTML chunk may contain variables, it may need a model… to be short it may be so complex as a MVC-PartialView.
To achieve the target quickly I tried to find something in the NET but without so much lucky. I have found a framework that seems to do the work @razorengine but, after a quick look at the code, I didn’t feel so enthusiast especially for the use we have to give to this new feature of our application (web application in Azure with something high traffic). hmm…. so ? what I can do ?
Personally I think that the MVC team in Microsoft is one of the best teams Microsoft ever had and I’m pretty sure there should be a way to parse a string using Razor without so much static classes… here we go with the Saturday proof of concept.
The code will talk by itself…
The VisualStudio solution
The base class
The beating heart of Razor is inside System.Web.Razor but to use it you need a class without any special reference, just some “special” methods.
{
public abstract class DynamicContentGeneratorBase
{
private StringBuilder buffer;
protected DynamicContentGeneratorBase()
{
DynModel = new ExpandoObject();
}
/// <summary>
/// This is just a custom property
/// </summary>
public dynamic DynModel { get; set; }
/// <summary>
/// This method is required and have to be exactly as declared here.
/// </summary>
public abstract void Execute();
/// <summary>
/// This method is required and can be public but have to have exactly the same signature
/// </summary>
protected void Write(object value)
{
WriteLiteral(value);
}
/// <summary>
/// This method is required and can be public but have to have exactly the same signature
/// </summary>
protected void WriteLiteral(object value)
{
buffer.Append(value);
}
/// <summary>
/// This method is just to have the rendered content without call Execute.
/// </summary>
/// <returns>The rendered content.</returns>
public string GetContent()
{
buffer = new StringBuilder(1024);
Execute();
return buffer.ToString();
}
}
}
Surprised ? yes!!… that is all you need.
The concept
With the System.Web.Razor.RazorTemplateEngine you can generate the source-code of concrete classes inherited from your abstract DynamicContentGeneratorBase. The generated code will have the concrete implementation of the abstract method Execute with the calls to the methods Write and WriteLiteral as required by your string. In practice giving a simple string as "<b>Well done @DynModel.Who !!</b>" the implementation of Execute, generated by Razor, will look like:
{
WriteLiteral("<b>Well done ");
Write(DynModel.Who);
WriteLiteral(" !!</b>");
}
To use the generated source you have to compile it in an assembly and create an instance of the concrete class.
Usage
- static void Main(string[] args)
- {
- const string dynamicallyGeneratedClassName = "DynamicContentTemplate";
- const string namespaceForDynamicClasses = "YourCompany";
- const string dynamicClassFullName = namespaceForDynamicClasses + "." + dynamicallyGeneratedClassName;
- var language = new CSharpRazorCodeLanguage();
- var host = new RazorEngineHost(language)
- {
- DefaultBaseClass = typeof(DynamicContentGeneratorBase).FullName,
- DefaultClassName = dynamicallyGeneratedClassName,
- DefaultNamespace = namespaceForDynamicClasses,
- };
- host.NamespaceImports.Add("System");
- host.NamespaceImports.Add("System.Dynamic");
- host.NamespaceImports.Add("System.Text");
- var engine = new RazorTemplateEngine(host);
- var tr = new StringReader(GetStringFromSomewhere()); // here is where the string come in place
- GeneratorResults razorTemplate = engine.GenerateCode(tr);
- var compiledAssembly = CreateCompiledAssemblyFor(razorTemplate.GeneratedCode);
- var templateInstance = (DynamicContentGeneratorBase)compiledAssembly.CreateInstance(dynamicClassFullName);
- templateInstance.DynModel.Who = "Fabio";
- Console.WriteLine(templateInstance.GetContent());
- Console.ReadLine();
- }
- private static Assembly CreateCompiledAssemblyFor(CodeCompileUnit unitToCompile)
- {
- var compilerParameters = new CompilerParameters();
- compilerParameters.ReferencedAssemblies.Add("System.dll");
- compilerParameters.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
- compilerParameters.ReferencedAssemblies.Add("System.Core.dll");
- compilerParameters.ReferencedAssemblies.Add(typeof(DynamicContentGeneratorBase).Assembly.Location);
- compilerParameters.GenerateInMemory = true;
- CompilerResults compilerResults = new CSharpCodeProvider().CompileAssemblyFromDom(compilerParameters, unitToCompile);
- Assembly compiledAssembly = compilerResults.CompiledAssembly;
- return compiledAssembly;
- }
- private static string GetStringFromSomewhere()
- {
- return "<b>Well done @DynModel.Who !!</b>";
- }
Form line 7 to 20 I’m generating the source of a concrete class called YourCompany.DynamicContentTemplate using the Razor template engine, then I’m compiling the source in an in-memory assembly, at line 24 I’m creating an instance of the YourCompany.DynamicContentTemplate, at line 25 I’m assigning a value to my DynamicModel and the result is: