XCentium Blogs
Technical
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:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<style>
.header, .footer, .body {
border: solid thin black;
height: 10%;
width: 100%;
}
.body {
margin: 10px;
}
</style>
</head>
<body>
<header class="header">
This is the common header for all of the emails sent from Insite
</header>
<div class="body">
@RenderBody()
</div>
<footer class="footer">
This is the common footer for all of the emails sent from Insite
</footer>
</body>
</html>
Email Template
@{
Layout = "~/Views/DefaultEmails/MainEmailLayout.cshtml";
}
<div class="email-wrapper">
<div class="body-copy">
<p>The password for your account on @Model.WebsiteUrl has been reset to:</p>
<p><strong>@Model.NewPassword</strong></p>
<p>Please visit <a href="@Model.WebsiteUrl">@Model.WebsiteUrl</a> to login and choose your own new password.</p>
</div>
</div>
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*?\"(?<filepath>.*?)\";\\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.