Generating HTML emails with RazorEngine

The ASP.NET team has developed a parser for Razor which is independent from ASP.NET. This means that

The ASP.NET team has developed a parser for Razor which is independent from ASP.NET. This means that Razor can be used outside the web applications to generate documents, for example, e-mails in HTML.

In this entry, we will describe how to create e-mails in HTML format using Razor’s parser, outside a web project, and using layouts and importing classes inside the templates. Using RazorEngine to generate e-mails through Razor is quite simple, although the documentation provided by this library is rather limited. The aim of this post is to provide a little more information.

Conventions

The code is based on RazorEngine 3.4, Razor 3.0. The whole code was written in C#. By convention, all the files for our templates are the type .cshtml – such as the ASP.NET MVC view. For MSBuid to be able to copy the file templates in the right place, it is necessary to set the property of the file “Copy to Output Directory” to “copy if it is newer”.

Step by step

RazorEngine gives us the class TemplateService to generate the desired document. The method used is Parse, which has 4 parameters.

  • The first parameter is a string which represents the template. In this case it is a constant, but it can also be obtained from a cshtml.
  • The second parameter is the model associated to the template.
  • The third parameter is a DynamicViewBag, which is not used in this explanation.
  • The last parameter is the name used to refer to the object in the cache.
To get to generate the document, RazorEngine takes the following steps:
• It parses the template and generates the font code of the class that creates the final document.
• It compiles this type “on the fly” inside its own assembly.
• It uploads the assembly again inside the app of the applications domain.
• It instantiates this new class.
• It uses it to generate the final document.

Steps 2 and 3 are the most difficult ones, even with a simple template it could be quite slow. Fortunately, RazorEngine solves this problem in a simple way: you just have to make sure you always specify a cache name when you call TemplateService.Parse ().

In the current version of RazorEngine (3.6), each instance of TemplateService keeps its own cache. Therefore, the same TemplateService instance could be used during the complete life cycle of the application. If you are using a DI container, TemplateService could be created as a Singleton instance.

EmailResolved = service.Parse(template, model, null, “template_cached”);

It should be taken into consideration that there can only be one template for each name chosen to specify the cache. Therefore, the only thing RazorEngine can do is to overwrite the values of the model on the template. However, if you want to use another template with the same name “template_cached” there will be an error, for example:

As a consequence, if it is necessary to optimize time, a cache entry is advisable for each e-mail template.

the cache.
To get to generate the document, RazorEngine takes the following steps:
• It parses the template and generates the font code of the class that creates the final document.
• It compiles this type “on the fly” inside its own assembly.
• It uploads the assembly again inside the app of the applications domain.
• It instantiates this new class.
• It uses it to generate the final document.

Steps 2 and 3 are the most difficult ones, even with a simple template it could be quite slow. Fortunately, RazorEngine solves this problem in a simple way: you just have to make sure you always specify a cache name when you call TemplateService.Parse ().

In the current version of RazorEngine (3.6), each instance of TemplateService keeps its own cache. Therefore, the same TemplateService instance could be used during the complete life cycle of the application. If you are using a DI container, TemplateService could be created as a Singleton instance.
It should be taken into consideration that there can only be one template for each name chosen to specify the cache. Therefore, the only thing RazorEngine can do is to overwrite the values of the model on the template. However, if you want to use another template with the same name “template_cached” there will be an error, for example:

As a consequence, if it is necessary to optimize time, a cache entry is advisable for each e-mail template.

Using namespaces

The template is initially turned into a font code file and then it is compiled dynamically invoking the compiler. Given that a font code can be used inside a template, it can use any library. However, the compiler should be able to solve them, and the default strategy is to make reference to all the assemblies previously uploaded.

This can bring about some problems, if in the template you want to use a library that was not referred to in the hosting code or if it was not uploaded in the execution time (since it is not used). To be able to solve this kind of issues, that behavior can be controlled and a custom implementation of the IReferenceResolver interface can be set. The following implementation was done:

Here a section is defined in the web.config, called “RazorAssemblies” with the dlls needed. If the assembly has already been uploaded, the reference should be added. Otherwise the assembly and the reference should be added. In the web.config, a section was added:

In which a further indirection level is added, so, if in the future it is necessary to add more libraries, the web.config will not be modified.

The RazorAssemblies.config file includes the following:

In the first example, it was explained that the class to be used is TemplateService. As a new IReferenceResolver implementation is to be added, when the instance is created it is necessary to pass the new configuration per parameter. To that end, the following method is defined, where the namespaces to be used by the templates are added, as well as the configuration of the new ReferenceResolver.

Creating Emails Using Layout

We define some constants in the following way, and then we define the following method.

The CreateEmail method receives as a parameter an object which is used in the template and the templatePath parameter, which is the path relative to the cshtml, from the corresponding email. Layouts have the following structure, please check that the value assigned to the layout of template.cshmtl is Header, the layout of Header is Footer, and the layout of Footer is Message. The service method GetTemplate saves all the templates.

Bear in mind that we have an instance of templateService for each execution, and that can be improved. If only one TemplateService is created for the whole application and there is a cached entry for each template, it will be much more efficient.

Footer.cshtml

Header.cshtml

Message.cshtml

And this is the body of the email.

This way, by using Layouts we can simplify our templates. We had to use this tool to send emails, and layouts templates proved to be very useful.

Bibliography:

Share this articleShare on LinkedInTweet about this on TwitterShare on FacebookShare on Google+Email this to someone
Go Back