Skip to main content
Home  ›  Blog

Tutorial: Use Razor to Template HTML Mails. It's great. You'll love it.

E-Mails must often be customized - and to do it well, we need to be able to place both values as well as complex logic (like tables with sums) into the e-mail. Let's not be DAFT - this can be done using Razor templates!

The Basics of Razor Mail Templates

Basically your code will

  1. instantiate a razor-engine - which will compile prepare your razor for your code
  2. run the razor together with the data you provide it - typically dynamic objects, dictionaries or something - and return a string with the result
  3. send the resulting string as an e-mail

Templating HTML Message

To make templating easy, it's best to use Razor with HTML, just like you would create 2sxc-views or MVC-views. This is best done using a helper, which is like a function, but you can also write HTML. Here's an example from the Mobius Forms App:

Example of Razor Helper Generating Mail

@helper Message(Dictionary<string,object> request, dynamic context)
{
    <!doctype html>
    <html>
    <head>
        <meta name="viewport" content="width=device-width">
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <style type="text/css">
            body { font-family: Helvetica, sans-serif; }
        </style>
    </head>
    <body>
        <h1>Website contact form request</h1>
        <table width="100%">
            @foreach (var item in request)
            {
                <tr>
                    <td width="10%"><b>@item.Key</b></td>
                    <td>@item.Value</td>
                </tr>
            }
        </table>
    </body>
</html>
}

You can get the code from the app-download, github or read the blog.

Templating the Subject

Most subjects are just a standard piece of text (not needing any code), but it's still nice to run it through code, just in case you want to add more logic. Since we don't need any HTML or line breaks, we should use a function and not a helper. Here's the example from the Mobius Forms App:

Example of Trivial Subject Function

@functions {
	public string Subject(dynamic request, dynamic context) {
                return context.Content.OwnerMailSubject.ToString();
	}
}

Connecting the Dots

So let's see the full recommendation. Here's one of the mail templates in the the Mobius Forms App. This example expects the caller (the WebApi) to pass the main data-object request and a context object helpers so the template can do it's job. Here's _Email customized.html:

Full _Mail to owner.cshtml

@helper Message(Dictionary<string,object> request, dynamic helpers)
{
    <!doctype html>
    <html>
    <head>
        <meta name="viewport" content="width=device-width">
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <style type="text/css">
            body { font-family: Helvetica, sans-serif; }
        </style>
    </head>
    <body>
        <h1>Website contact form request</h1>
        <table width="100%">
            @foreach (var item in request)
            {
                <tr>
                    <td width="10%"><b>@item.Key</b></td>
                    <td>@item.Value</td>
                </tr>
            }
        </table>
    </body>
</html>
}

@functions {
	public string Subject(dynamic request, dynamic helpers) {
        // create custom code to generate the subject here...

        // or just return the setting configured in the form
        return !String.IsNullOrWhiteSpace(helpers.Content.OwnerMailSubject)
            ? helpers.Content.OwnerMailSubject.ToString()
            : helpers.App.Settings.OwnerMailSubject.ToString();
	}
}

And here's the code exerpt from the api/FormController.cs WebApi, which calls up the rendering code:

Extract from the api/FormController.cs

using ...;

public class FormController : SxcApiController
{
    
	[HttpPost]
    [DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.Anonymous)]
    [ValidateAntiForgeryToken]
    public void ProcessForm([FromBody]Dictionary<string,object> contactFormRequest)
    {
        // ...
        // 3. Send Mail to owner
        // uses the DNN command: http://www.dnnsoftware.com/dnn-api/html/886d0ac8-45e8-6472-455a-a7adced60ada.htm
        var ownerMailEngine = TemplateInstance(config.OwnerMailTemplate);
        var ownerBody = ownerMailEngine.Message(valuesWithMailLabels, this).ToString();
        var ownerSubj = ownerMailEngine.Subject(valuesWithMailLabels, this);
        // ...
    }

    private dynamic TemplateInstance(string fileName)
    {
        var compiledType = BuildManager.GetCompiledType(System.IO.Path.Combine("~", App.Path, "email-templates", fileName));
        object objectValue = null;
        if (compiledType != null)
        {
            objectValue = RuntimeHelpers.GetObjectValue(Activator.CreateInstance(compiledType));
            return ((dynamic)objectValue);
        }
        throw new Exception("Error while creating mail template instance.");
    }
}

TL;DR

In the name of Don't be DAFT we really believe that standard technologies should be used by techies to solve advanced problems, and that this is better than trying to provide an abstraction which will always be missing just-that-one-feature.

We hope you enjoy it!
Daniel (iJungleboy)

PS: you can always get the full code example from the Mobius Forms App (download, check Github or read blog).

 


Daniel Mettler grew up in the jungles of Indonesia and is founder and CEO of 2sic internet solutions in Switzerland and Liechtenstein, an 20-head web specialist with over 800 DNN projects since 1999. He is also chief architect of 2sxc (see github), an open source module for creating attractive content and DNN Apps.

Read more posts by Daniel Mettler