Posts tagged RazorEngine

Generating HTML emails with RazorEngine - Part 03 - Caching, VS integration & namespace config

This is the third part of a 10-part blog series. You'll find a list of all the posts in this series in the introductory post. Make sure to review the Before we start section in the introductory post.

All the source code is on GitHub. Comment? Bug? Open an issue on GitHub.


Caching templates with RazorEngine

When generating our email in the previous post, we happily ignored the cacheName parameter of the TemplateService.Parse() method. This was wrong.

Not using RazorEngine's cache will result in both dreadful performances and memory leaks.

Generating a document from a Razor template involves some very expensive operations. We'll cover this in more details in the next post but in a nutshell, TemplateService.Parse() will:

  1. Parse your template and generate the source code of a class that can generate the final document.
  2. Compile that class on the fly into its own assembly.
  3. Load that newly created assembly into your app domain.
  4. Instantiate the new class.
  5. Use that new class to generate the final document.

Steps 2 and 3 are the expensive ones. Even with a trivially simple template like the one we used in the previous post, the whole process takes more 100ms on my machine. Throw in a layout and a few partials and the process could easily take more than a second.

It also leads to a memory leak as assemblies can't be unloaded from the primary app domain.

Thankfully, RazorEngine makes it trivial to fix this. Simply make sure to always specify a cache name when calling TemplateService.Parse():

var emailHtmlBody = templateService.Parse(welcomeEmailTemplate, model, null, "WelcomeEmail");  

RazorEngine will then keep the generated class instance in its cache and re-use it whenever you call Parse() with the same cache name instead of parsing your template again for every call to Parse(). In practice, once a template has been cached, calling TemplateService.Parse() with that same template and cache name is pretty much instant.

IMPORTANT: In the current version of RazorEngine (3.4), each TemplateService instance maintain their own cache. You therefore must use the same instance of TemplateService throughout the lifetime of your application. If you're using a DI container, configure TemplateService to be created with a Singleton lifetime.

The project for Part 03 on GitHub includes a quick benchmark demonstrating the effect of using the cache in RazorEngine.

Visual Studio integration

Fixing the Razor intellisense

When we edited the Razor template in the previous post, the Visual Studio editor complained about @model with:

Cannot resolve symbol 'model'

It also didn't provide any intellisense when working with the model. This is because the Visual Studio editor relies on classes in the System.Web.Razor assembly (and a few others) to parse Razor files and provide Razor intellisense. And we didn't have these assemblies referenced in our project.

This is easy to fix:

PM> Install-Package Microsoft.AspNet.Mvc

This will reference all the necessary assemblies.

If you're curious about how the Visual Studio editor works behind the scenes with Razor templates, Andrew Nurse, the main developer of the ASP.NET Razor View Engine, has left a few hints in this blog post.

Getting rid of the squiggly blue lines

The VS editor also threw a rather cryptic error:

ASP.NET runtime error: There is no build provider registered for the extension '.cshtml'. You can register one in the <compilation><buildProviders> section in machine.config or web.config. Make sure is has (sic) a BuildProviderAppliesToAttribute attribute which includes the value 'Web' or 'All'.

This is easily fixed by adding a Web.config file at the root of your project containing:

Yes, a Web.config file, even if you're within a console, desktop or Windows Service project. The file will only be used at design-time by the Visual Studio editor and will be ignored at runtime.

Getting the VS editor to use the RazorEngine base class instead of the one used by ASP.NET MVC

By default, the Visual Studio editor assumes that a Razor file will be used to generate an ASP.NET MVC view. So its intellisense assumes that all the C# code in the file will be part of a class deriving from the System.Web.Mvc.WebViewPage class.

RazorEngine however provides its own base class RazorEngine.Templating.TemplateBase instead. Although TemplateBase implements many of the same properties and methods that WebViewPage has, there are some differences. For example, TemplateBase has an Include() method (used to render partials) that WebViewPage doesn't have. If you were to use the Include() method in your email template, the VS editor would highlight it as a syntax error even though it is a perfectly legal call if the template is meant to be parsed and compiled by RazorEngine.

You can ask the VS editor to use a different base class. Add a Web.config file at the root of your project if you haven't already done so (and yes, again, a Web.config file even in a console, desktop or Windows Service project). Add this to it:

You might have to close and re-open your .cshtml file after having done this for the VS editor to take the config into consideration.

Configuring namespaces

In the previous post, we mentioned that it was mandatory to always use fully-qualified type names for any class used in a Razor template (or explicitely import namespace with the @using directive). Failure to do so would result in a TemplateCompilationException at runtime.

This can become tiring, especially if there are namespaces that you use in every single template (e.g. the namespace containing your model classes).

But you can easily configure your TemplateService instance to include additional namespaces into the generated template classes by default:

templateService.AddNamespace("ConsoleApplication.Models");  

Annoyingly, since the Visual Studio editor won't know about these default namespaces, it will still complain about unknown types if you don't excplicitely import all the namespaces in your template files.

You can work around this by adding the extra namespaces in the <system.web.webPages.razor><pages><namespaces> section of the Web.config file (not App.config) as you would do with the ASP.NET MVC Razor View Engine:

Note: RazorEngine also has a App.config-based configuration model. This would in theory allow you to define the default namespaces in your App.config instead of hardcoding them in code. In practice, RazorEngine's XML confguration code seems to be quite broken making it more trouble than it's worth to go down that route.

A better way to configure default namespaces

Having to configure the default namespaces twice, once in code and once in the Web.config file as described above, is hardly ideal. Wouldn't it be nice if we could define the default namespaces just once in the ASP.NET MVC configuration section in Web.config as shown above and simply use this list to configure our TemplateService instance?

Well, it's possible and it's quite easy too.

First, we need to ensure that our Web.config file gets deployed alongside the application. If you're within a console, desktop or Windows Service project, the Web.config file is ignored by MSBuild and not deployed by default. All we need to do to fix this is set the Copy to Output Directory property of our Web.config file to Copy if newer:

We can now just parse this configuration file at runtime, extract the list of namespaces and add them to our TemplateService instance:

var webConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web.config");  
var fileMap = new ExeConfigurationFileMap() { ExeConfigFilename = webConfigPath };  
var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);  
var razorConfig = configuration.GetSection("system.web.webPages.razor/pages") as RazorPagesSection;

foreach (NamespaceInfo namespaceInfo in razorConfig.Namespaces)  
{
     templateService.AddNamespace(namespaceInfo.Namespace);
}

And we're done. The default namespaces are now defined in a single location.

In the next post, we'll take a more detailled look at how the Razor parser and RazorEngine work behind the scene.

Generating HTML emails with RazorEngine - Part 02 - Basics: generating your first email

This is the second part of a 10-part blog series. You'll find a list of all the posts in this series in the introductory post. Make sure to review the Before we start section in the introductory post.

All the source code is on GitHub. Comment? Bug? Open an issue on GitHub.


As indicated in the introductory post, we'll work from a console application.

The email template

For this example, we'll generate a simple Welcome email. We'll start with a basic model for our email:

namespace ConsoleApplication.Models  
{
    public class UserModel
    {
        public string Name { get; set; }
        public string Email { get; set; }
        public bool IsPremiumUser { get; set; }
    }
}

We can then implement our email template in the same way as we would implement an ASP.NET MVC Razor view. Here, we'll use a strongly-typed model:

@model ConsoleApplication.Models.UserModel

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">  
<head>  
    <meta charset="utf-8" />
    <title>Welcome</title>
</head>  
<body>  
    <p>
        Hi @Model.Name,
    </p>
    <p>
        Thanks for signing up to <a href="http://awesomesite.com">our awesome site</a>!
    </p>

    @if (!Model.IsPremiumUser)
    {
        <p>
            To make the most of it, <a href="http://awesomesite.com/upgrade">upgrade to our premium package now</a>!
        </p>
    }

    <p>John, Founder</p>

</body>  
</html>  

As mentioned in the introductory post, place this template in a .cshtml file. Don't worry about any error or warning message the Visual Studio editor might throw at you. They are just a design-time annoyance and won't prevent your app from compiling and running successfully. We'll look at how to get rid of them in the next post.

RazorEngine also supports dynamic models in the same way ASP.NET MVC's Razor View Engine does. The code on GitHub has an example of this.

Note: in your email templates, make sure to always either use fully-qualified type names or import the necessary namespaces with the @using directive. If your template references a class that isn't fully-qualified and that hasn't had its namespace imported, you will get a TemplateCompilationException at runtime. We'll explain why this happens and how we can make things better in the next couple of posts.

Generating the email's HTML body

Generating your email from this template is a simple matter or instantiating RazorEngine's TemplateService class and calling its Parse() method passing the content of the template and your model as parameters:

You're done!

IMPORTANT: Before you go off, make sure that you read at least the next post in this series as there are a few essential features of RazorEngine that you must know in order to use it properly and effectively.

Note: In addition to the TemplateService class, the RazorEngine library also exposes Razor, a static class that implements a very thin facade over the TemplateService interface. For some reason, most of the RazorEngine examples you'll find on the web use the Razor class instead of directly using TemplateService.

I don't really see any reason for using the Razor class so we won't using it in this series. Since Razor is a static class, using it means hiding the RazorEngine dependency deep down the implementation, preventing you from being able to surface this dependency at the class interface level. This complicates testing and maintenance. In addition, most methods in Razor are one-liners that map directly to methods of the same name in TemplateService so using Razor doesn't simplify or shorten the code. As far as I can see, Razor only adds another and largely uncessary level of abstraction.

Things that won't work

That are a number of functionalities that won't work out of the box with RazorEngine, or at least not in the same way as they do with the ASP.NET MVC Razor View Engine. This includes layouts, partials and URL generation. We'll look at how to get these functionalities with RazorEngine in the next posts of this series.

Sending the email

This of course isn't specific to RazorEngine, but the sake of completeness let's send this email.

First add your SMTP settings to your App.config file. We'll include sender information there as well so that we don't have to hardcode it:

You'll probably have different SMTP settings in development and in production. Thankfully, thanks to the awesome job that Sayed Ibrahim Hashimi and Chuck England have done with their SlowCheetah Visual Studio add-on, you can get App.config transforms for your console app (or desktop app or Windows Service) that work in the exact same way as Web.config transform work for ASP.NET applications.

So add SlowCheetah to your project:

PM> Install-Package SlowCheetah

Then right-click your App.config and choose Add Transform. This will add a Debug and a Release transform for your App.config file. Simply add your production settings in the App.Release.config file:

And send:

// Send the email
var email = new MailMessage()  
{
    Body = emailHtmlBody,
    IsBodyHtml = true,
    Subject = "Welcome"
};

email.To.Add(new MailAddress(model.Email, model.Name));  
// The From field will be populated from the app.config value by default

var smtpClient = new SmtpClient();  
smtpClient.Send(email);  

Run and if you're using smtp4dev, you should now see the new message pop-up:

Of course, in a real-world application, you'll probably be sending emails asynchronously:

await smtpClient.SendMailAsync(email);  

In the next post of this series, we'll look at a few more basic but essential features of RazorEngine that will speed up email generation and make your life easier.

Generating HTML emails with RazorEngine - Part 01 - Introduction


Blog posts in this series:
  1. Introduction (this post)
  2. Basics: generating your first email
  3. More Basics: caching, VS integration & namespace configuration
  4. Taking a step back: what is Razor and what does RazorEngine actually do?
  5. URL generation & T4MVC integration with RazorEngine (TBD)
  6. Layout with RazorEngine (TBD)
  7. Partials with RazorEngine (TBD)
  8. On keeping your sanity: inlining CSS with PreMailer.Net (TBD)
  9. Getting a little help from CsQuery: including subject, sender and recipient information in your email templates (TBD)
  10. Putting it all together: building a complete email-generation library with RazorEngine, PreMailer.Net and CsQuery (TBD)

I can think of few things in my life as a software developer that have caused me more grief than having to generate HTML emails. I long for the days when you could still argue in favour of plain text emails with clients.

HTML emails are a strange kind of beast. On one side, they're part of just about every web application out there and are often the most unrewarding feature to implement. On the other, they are technically stuck in the mid-90's, are unbelievably painful to implement and are a monstrosity to maintain. And yet, there is very little tooling available to make implementing them less painful.

In the .NET world, things have become slightly better with the introduction of the Razor view engine in ASP.NET MVC 3. Razor has made implementing HTML views in web application almost pleasant. And the ASP.NET team have had the great insight of making the Razor parser independent from ASP.NET, which means that you can use it outside of web applications to generate any type of document. Including HTML emails.

Combined with with the awesome PreMailer.Net library, Razor makes it orders of magnitude easier to implement and maintain dynamically-generated HTML emails compared to the String.Replace() method we've all been using (or, god forbid, XSLT).

Several Razor-based email-generation libraries have poped-up since the introduction of Razor. Postal, MvcMailer and ActionMailer.Net are probably the most popular ones. I've tried several but couldn't find one that I was really happy with. Some only work within the context of an ASP.NET application, others don't play nice with DI containers, others have too many limitations or bugs. I ended up spending more time pouring through their source code to try and understand what was going on than it would have taken me to implement my own solution.

And it turns out that implementing your own Razor-based email-generation library is fairly trivial thanks to the great job that Matthew Abbott did with his RazorEngine library, a wrapper around the Razor parser that makes it easy to use Razor anywhere. Unfortunately, the documentation for RazorEngine is somewhat lacking. So we'll try to address this here.

In this series of blog posts, I'll take you through implementing your own email-generation library with RazorEngine that will let you generate and send emails from any type of application: ASP.NET, console or desktop apps or even Windows Services. RazorEngine lets you implement your emails templates in the exact same way as you would implement an ASP.NET MVC view, including support for layouts, partials, strongly-typed and dynamic models, URL generation and even T4MVC integration.

Before we start

Accompanying code

You can get the code on GitHub.

Essential utilities

If you're implementing and testing emails on Windows and not already using smtp4dev, go and download it right now.

Conventions

The code will be based on RazorEngine 3.4, Razor 3.0 and ASP.NET MVC 5.

In this series of blog post and to keep things simple, we'll be generating emails from a console application. Unless otherwise noted, the code should work in the exact same way within the context of an ASP.NET application, a desktop application or a Windows Service.

All the code and Razor templates will be in C# but can be easily adapted for VB.NET.

By convention, we'll suffix our email template files with the .cshtml extension (just like ASP.NET MVC views) and put them in a folder called EmailTemplates at the root of the application (i.e. next to the executable for console / desktop apps or Windows Services and at the root of the web app for ASP.NET apps).

To let MSBuild copy your email template files to the right place at build time, place them in an EmailTemplates folder at the root of your project and set the Copy to Output Directory property of all template files to Copy if newer:

To retrieve the path of our email template files in code, we'll use the AppDomain.CurrentDomain.BaseDirectory property. In console apps, desktop apps and Windows Services, this points to the executable's directory (unless you're doing something funky with app domains). Within ASP.NET applications, where the app domain is created by IIS, that property is set to the root of the web app.

The full path of the EmailTemplates folder can be retrieved easily:

var templateFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EmailTemplates");

Finally, you'll of course need to add a reference to the RazorEngine library in your project:

PM> Install-Package RazorEngine

In the next post, we'll generate our first Welcome email.