Sending personalised emails with the Razor templating engine

30 november 2012 om 00:00 by Martijn van der Put - Post a comment

In almost every Sitecore implementation there is the need to send personalised emails to the website visitor after filling in a webform, making a purchase or triggering some other kind of action which involves feedback through an email message.

A lot of times the emailtexts are completely generated through .NET code or by doing a replace-action on a textfield in Sitecore.

Solution

A more flexible way to do this, is by using the Razor Templating Engine.

By using RazorEngine you can extend the content of emails with existing properties in your data-object without the need of re-programming your code and a .dll update on your website.

Implementation

In this case we generate a personalised email with product specifications which was triggered by the website visitor through a request-for-product-details form.

  1. Download RazorEngine (http://razorengine.codeplex.com) and put the files RazorEngine.dll and System.Web.Razor.dll in your \bin\ directory. In your project you need to only reference the RazorEngine.dll.

  2. Create a datatemplate in Sitecore which holds the e-mail fields (like to, from, cc, bcc, subject and body) and create a content item in the content tree, based on this template. For the Body field it is best to keep this as a Multi-Line Textfield instead of a Rich Text field because of the Razorcode we are going to add later on.
    You also might want to add a style property “height:500px;” to the Body-field in the datatemplate to enlarge the field-height in the content editor.

  3. Create a class which will hold the data for the Razor Model. In this case it is the EmailProductData class.
    
    /* 
    This class contains the data that will be used as the Model in 
    the Razor Engine and can be filled with information of the 
    registered user and chosen product 
    */
    
    Public class EmailProductData
    {
      public ProductData Product { get; set; }
    
      public FormFieldsData FormFields { get ; set; }
    
      public class ProductData 
      {
        public string Name { get; set; }
        public string Width { get; set; }
        public string Heigth { get; set; }
        public string Depth { get; set; }
        public string Price { get; set; }
      }
    
      public class FormFieldsData
      {
        public string FirstName { get; set; }
      }
    }
    
  4. Next, create an EmailTemplateManager class which will hold the code to generate the email, parse the RazorCode and finally send the email.
    
    public class EmailTemplateManager
    {
      /*
      First of all this class holds properties for the 
      fields in the Sitecore Data template and an object 
      property “modelObject” which contains 
      all the product data.
      */ 
    
       private string _from { get; set; }    
       private string _to { get; set; }    
       private string _cc { get; set; }    
       private string _bcc { get; set; }    
       private string _subject { get; set; }    
       private string _message { get; set; }
    
       private object _modelObject { get; set; }
    
      /*
      The constructor will call the SetEmailProperties method 
      which will read the fields from the EmailTemplate item 
      and set the modelObject. In this case the modelObject 
      is an Object of type EmailProductData.
      */
    
      public EmailTemplateManager(Item emailTemplateItem, object modelObject)
      {
         // set email properties
         SetEmailProperties(emailTemplateItem);
        _modelObject = modelObject;
      }
    
      private void SetEmailProperties(Item emailTemplateItem)
       {
        /* set the email properties (to, cc, bcc, subject, message, etc) */
    
        if (emailTemplateItem != null 
          && emailTemplateItem.TemplateID.ToString().Equals(_emailTemplateID))
        {
           _from = emailTemplateItem["From"];
           _to = emailTemplateItem["To"];
           _cc = emailTemplateItem["CC"];
           _bcc = emailTemplateItem["BCC"];
           _subject = emailTemplateItem["Subject"];
           _message = emailTemplateItem["Message"];
        }
    
       }
    
      /*
      This method parses the RazorTemplate from the fields of the Sitecore item
      */
      private string ParseRazorTemplate(string value)
      {
         string result = string.Empty;
    
        if (!string.IsNullOrWhiteSpace(value) && _modelObject != null)
        {
          /* Create a dynamic object that can do private reflection 
            over the model object */
          model = new ReflectionDynamicObject() { RealObject = _modelObject };
          result = Razor.Parse(value, model);
        }
    
        return result;
      }
    
      /* Create a Dynamic object  */ 
      public dynamic model { get; private set; }
     
      private class ReflectionDynamicObject : DynamicObject     
      {
     
         internal object RealObject { get; set; }
    
         public override bool TryGetMember(GetMemberBinder binder, 
            out object result)     
         {
    
          // Get the property value     
          result = RealObject.GetType().InvokeMember(binder.Name, 
            BindingFlags.GetProperty | BindingFlags.Instance 
            | BindingFlags.Public | BindingFlags.NonPublic, 
            null, RealObject, null);
     
          /* Always return true, since InvokeMember would have thrown 
            if something went wrong */
          return true;    
         }
     
      }
     
      /*
      This is where the magic happens
      */
      public bool SendMessage()     
       {
    
         bool result = false;
     
        /*
        Parse all Razor code in the item fields
        */
     
        try    
        {    
          _from = ParseRazorTemplate(_from);    
          _to = ParseRazorTemplate(_to);    
          _subject = ParseRazorTemplate(_subject);    
          _message = ParseRazorTemplate(_message);    
          MailMessage mailMessage = 
              new MailMessage(_from, _to, _subject, _message);
          mailMessage.IsBodyHtml = true;
    
          // add extra properties    
          if (!string.IsNullOrWhiteSpace(_cc))    
          {    
            _cc = ParseRazorTemplate(_cc);    
            mailMessage.CC.Add(_cc);    
          }
    
          if (!string.IsNullOrWhiteSpace(_bcc))    
          {    
            _bcc = ParseRazorTemplate(_bcc);    
            mailMessage.Bcc.Add(_bcc);    
          }
    
          // Send the email    
          Sitecore.MainUtil.SendMail(mailMessage);    
          result = true;    
        }
    
        catch (TemplateCompilationException ex)    
        {    
          // catch and log Razor template compilation errors    
        }
    
        catch (Exception ex)    
        {    
          // catch and log other exceptions    
        }       
    
         return result;
    
       }     
    }
    

  5. Create the bodytext in the Sitecore Body-field by using html and Razor code.
    
    @{     
       var product = @Model.Product;     
       var formFields = @Model.FormFields;
     
    <html>     
    <body>
     
    <style type="text/css">     
      body {     
        color: #0000FF;     
        font-size: 12px;     
      }     
    </style>
     
    <p>Dear @formFields.FirstName,<br />    
    <br />
    You have requested detailed information about product @product.Name.<br />
    <br />
    Description: @product.Description<br />     
    Size (hxwxd): @product.Height mm x @product.Width mm x @product.Depth<br />
    Price: @product.Price<br />
    <br />
    </p>     
    </body>     
    </html>     
    }
    
    Finally, when we want to send an email, we need to create an instance of the EmailTemplateManager and call the Send() method
    
    EmailTemplateManager templateManager = 
        new EmailTemplateManager(emailTemplate, emailProductData);
    
    templateManager.SendMessage();
    

    The email template can be easily extended with other properties that are available in our Model.

Creating helper methods in .NET

With the Razor Templating Engine it is possible to create helper methods in .NET which can take parameters from your object Model to create functionalities that you don't have in Razor.


/*
We need a class which contains the helper functions
*/
public abstract class RazorHelperMethods : TemplateBase 
{
  public string ToUpper(string value)
  {
    // this can be any function you need
    return value.ToUpperCase();
  }
}

/*
In the EmailTemplateManager class we add a property 
_razorBaseTemplateType, which is set in the constructor
*/
private Type _razorBaseTemplateType { get; set; }
  
public EmailTemplateManager(string itemPath, object dataObject, 
          Type razorBaseTemplateType)
{ 
  // get the emailtemplate item 
  Item emailTemplateItem = Sitecore.Context.Database.GetItem(itemPath);
 
  // set email properties 
  if (emailTemplateItem != null) 
  { 
     SetEmailProperties(emailTemplateItem); 
  }
 
  _razorBaseTemplateType = razorBaseTemplateType; 
  _dataObject = dataObject; 
}

/*
Before we parse any fields, we set the BaseTemplate
*/ 
  private string ParseRazorTemplate(string value)
  {
     string result = string.Empty;

    if (!string.IsNullOrWhiteSpace(value) && _modelObject != null)
    {    
      // optional: set the Razor Base Template with helper functions
      if (_razorBaseTemplateType != null)
      {
        Razor.SetTemplateBase(_razorBaseTemplateType);
      }

      /* Create a dynamic object that can do private reflection 
        over the model object */
      model = new ReflectionDynamicObject() { RealObject = _modelObject };
      result = Razor.Parse(value, model);
    }

    return result;
  }

/*
When we create an instance of the EmailTemplateManger, 
we must pass the BaseTemplate
*/
EmailTemplateManager templateManager = new EmailTemplateManager(
         emailTemplate, emailProductData, typeof (RazorHelperMethods<>));

templateManager.SendMessage();

This method is extremely powerful for medium to complex personalised emails without the need to modify your sourcecode and rebuild and deploy new .dll's.

Nieuwste