Jasinski Technical Wiki

Navigation

Home Page
Index
All Pages

Quick Search
»
Advanced Search »

Contributor Links

Create a new Page
Administration
File Management
Login/Logout
Your Profile

Other Wiki Sections

Software

PoweredBy

Page History: Email Templating Using the ASP.NET Razor View Engine

Compare Page Revisions



« Older Revision - Back to Page History - Newer Revision »


Page Revision: Thu, May 05, 2016, 7:58 AM


Overview

A common design pattern is for an application to send out "form letters" via email using a template. Typically these template contain include some sort of token, where each token gets replaced with relevant data at the time the email is to be sent. An example of this might be a "Forgot Password" email, where the username, etc. is to be included in the body of the email. The email template may include a token such as "{USERNAME}" which is replaced with the actual username when the email is sent.

While this works fine for simple emails, for more complex emails this token mechanism becomes cumbersome and difficult to maintain. This is especially true when the data to be presented is a collection (e.g., multiple rows of query results) or if the presentation logic involves some decision making. The ASP.NET Razor View Engine was built to handle just such a use case. This article presents code which uses the ASP.NET Razor View Engine outside the context of an ASP.NET website to transform any given .NET object into HTML, which can then be used as the body of an email message.

Reusable Code

The following code was adapted from http://vibrantcode.com/blog/2010/11/16/hosting-razor-outside-of-aspnet-revised-for-mvc3-rc.html

TemplateBase Class

using System;
using System.ComponentModel;
using System.IO;
using System.Text;

public abstract class TemplateBase<TModel>
{
    [Browsable(false)]
    public StringBuilder Buffer { get; set; }

    [Browsable(false)]
    public StringWriter Writer { get; set; }

    public TemplateBase()
    {
        Buffer = new StringBuilder();
        Writer = new StringWriter(Buffer);
    }

    public abstract void Execute();

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

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

    public TModel Model { get; set; }
}

TemplateEngine Class

using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web.Razor;
using Microsoft.CSharp;

/* Adapted from http://vibrantcode.com/blog/2010/11/16/hosting-razor-outside-of-aspnet-revised-for-mvc3-rc.html 
 * and https://gist.github.com/ArnoldZokas/2204352
 * and http://www.codemag.com/article/1103081
 * 
 * with acknowledgement to Jeff Polakiewicz for the inspiration behind the idea 
 * and to Andy Hopper for technical assistance.
 */
public class TemplateEngine<TModel>
{
    private TemplateBase<TModel> _currentTemplate;
    private RazorTemplateEngine _razor;

    private void Init()
    {
        RazorEngineHost host = new RazorEngineHost(new CSharpRazorCodeLanguage());
        host.DefaultBaseClass = typeof(TemplateBase<TModel>).FullName;
        host.DefaultNamespace = "RazorOutput";
        host.DefaultClassName = "Template";
        host.NamespaceImports.Add("System");
        _razor = new RazorTemplateEngine(host);
    }

    public string Execute(string templateContent, TModel model, params Assembly[] referenceAssemblies)
    {
        Init();
        _currentTemplate = null;

        // Generate code for the template
        GeneratorResults razorResult = null;

        /* Because the [@model] syntax is ASP.NET-MVC-specific, remove any line starting with "@model" */
        templateContent = string.Join("\n\r", templateContent.Split(new char[] { '\n', '\r' }).Where(o => !o.StartsWith("@model ")));

        using (TextReader rdr = new StringReader(templateContent))
        {
            razorResult = _razor.GenerateCode(rdr);
        }

        // Compile the generated code into an assembly
        var compilerParameters = new CompilerParameters();
        compilerParameters.GenerateInMemory = true;
        compilerParameters.ReferencedAssemblies.Add(typeof(TemplateEngine<TModel>).Assembly.Location);

        if (referenceAssemblies != null)
        {
            foreach (var referenceAssembly in referenceAssemblies)
            {
                compilerParameters.ReferencedAssemblies.Add(referenceAssembly.Location);
            }
        }

        var codeProvider = new CSharpCodeProvider();

        CompilerResults results = codeProvider.CompileAssemblyFromDom(compilerParameters,
            razorResult.GeneratedCode);

        if (results.Errors.HasErrors)
        {
            CompilerError err = results.Errors
                                        .OfType<CompilerError>()
                                        .Where(ce => !ce.IsWarning)
                                        .First();

            throw new Exception(String.Format("Error Compiling Template: ({0}, {1}) {2}",
                                            err.Line, err.Column, err.ErrorText));
        }
        else
        {
            // Load the assembly
            var asm = results.CompiledAssembly;
            if (asm == null)
            {
                throw new Exception("Error loading template assembly");
            }
            else
            {
                // Get the template type
                Type typ = asm.GetType("RazorOutput.Template");
                if (typ == null)
                {
                    throw new Exception(string.Format("Could not find type RazorOutput.Template in assembly {0}", asm.FullName));
                }
                else
                {
                    TemplateBase<TModel> newTemplate = Activator.CreateInstance(typ) as TemplateBase<TModel>;
                    if (newTemplate == null)
                    {
                        throw new Exception("Could not construct RazorOutput.Template or it does not inherit from TemplateBase");
                    }
                    else
                    {
                        _currentTemplate = newTemplate;
                        _currentTemplate.Model = model;
                        _currentTemplate.Execute();
                        var result = _currentTemplate.Buffer.ToString();
                        _currentTemplate.Buffer.Clear();
                        return result;

                    }
                }
            }
        }
    }
}

ScrewTurn Wiki version 3.0.1.400. Some of the icons created by FamFamFam. Except where noted, all contents Copyright © 1999-2019, Patrick Jasinski.