Using Razor Layouts With Insite Emails
Insite's EmailService allows a developer to easily send emails to a customer whenever they perform an action on the system. This EmailService utilizes the RazorEngine in order to parse HTML and generate rich content emails for delivery. On a recent project, I had many scenarios where an email was being sent to the customer. All of the emails contained a common look and feel, including a fully rendered header and footer. In this post, I will go over the process I followed in order to allow for the use of the standard Layout property in the Razor template for emails.
When the email templates were first created, I immediately tried to utilize the standard Layout property for a Razor view in order specify which content it should use.
Layout File:
@ViewBag.Title
This is the common header for all of the emails sent from Insite
@RenderBody()
Email Template
@{
Layout = "~/Views/DefaultEmails/MainEmailLayout.cshtml";
}
The password for your account on @Model.WebsiteUrl has been reset to:
@Model.NewPassword
Please visit @Model.WebsiteUrl to login and choose your own new password.
Unfortunately, this immediately started generating errors in the EmailService.
Turns out, the relative path specified is a part of the Insite web application, and isn't resolvable from the context in which the EmailService executes. After looking through the OOTB EmailService code, I was able to determine that the content parser could be customized.
public class EmailServiceRio : EmailService
{
public EmailServiceRio(IEmailTemplateUtilities emailTemplateUtilities, IContentManagerUtilities contentManagerUtilities, IEntityTranslationService entityTranslationService)
: base(emailTemplateUtilities, contentManagerUtilities, entityTranslationService)
{
}
// Insite apparently has issue with using Layout in the Razor.
// We are overriding their parse template method to manually resolve and inject the Layout content.
public override string ParseTemplate(string template, ExpandoObject model, string templateName)
{
#region Customize - Allow for Layouts in templates
var noLineTemplate = template.Replace("\r", "").Replace("\n", "").Replace("\t", "");
// @{ Layout = "~/Views/DefaultEmails/EmailMainLayout.cshtml"; }
var layoutRegex = new Regex("@{\\s*?Layout\\s*?=\\s*?\"(?.*?)\";\\s*?}");
// If we are using a layout
if (layoutRegex.IsMatch(noLineTemplate))
{
var filePath = layoutRegex.Match(noLineTemplate).Groups["filePath"].Value;
noLineTemplate = layoutRegex.Replace(noLineTemplate, string.Empty);
var layoutLines = string.Join(Environment.NewLine, File.ReadLines(HostingEnvironment.MapPath(filePath)).ToList());
template = layoutLines.Replace("@RenderBody()", noLineTemplate);
}
#endregion
var content = Razor.Parse(template, model, templateName);
return content;
}
}
The above code uses a Regular Expression and some basic string manipulation magic in order to detect and resolve a layout file being referenced in a email template. As mentioned above, the normal execution context of the EmailService means that it will not have access to a web based relative path to retrieve the content. In order to get the file, I need to get the fully qualified path for the layout file (e.g. C:\Projects\InsiteCommerce-4.2.0\InsiteCommerce.Web\Views\DefaultEmail\MainEmailLayout.cshtml). Once I have the fully qualified path for the layout, I can manually read the file off of the server. By simply replacing "@RenderBody()" from the layout file with the original template content, I now have a fully formed email ready to be parsed and delivered.
There you have it. With the help of regular expressions and a some string manipulations, I now how a solution that allows me to utilize Razor Layouts in my email templates, saving me a lot of work when modifying the content of my emails.
The example used in this blog post has been implemented on Insite version 4.2.0.34172, a MVC application running on .NET Framework 4.5.2.