Using LINQ to do email templates

Saturday, Sep 27, 2008 3 minute read Tags: linq
Hey, thanks for the interest in this post, but just letting you know that it is over 3 years old, so the content in here may not be accurate.

So recently I was working on project where a client wanted to have customisable email templates which could be merged with data from their database so we store the email as an XML document and have a series of placeholders within it to allow easy editing to customise the wording, layout, etc.

But because there's quite a lot of different email "data sources" we wanted a nice and easy way so we didn't have to constantly write merge methods, having a single method which handles it all is the best idea.
But how do we handle all the different data sources, and since the ORM is LINQ to SQL it'd be really nice to not have to constantly write classes and structures to handle all the difference formats. So this is what I come up with.

Step 1, an XML document

This isn't really that complex a step, I've got it really primitive and the XML was only storing the subject and body. But it can be as complex as required, storing SMTP details, sender, recipient(s), etc.

I have just 2 nodes, a Subject and a Body node, the Body of the email being stored in a CDATA to make it easier to parse.

Step 2, the Email Template class

Now we need a class for the email template generation, this is what I have:

public class EmailTemplate{
public string Subject { get; private set; }
public string Body { get; private set; }
public EmailTemplate(string path){
XDocument xdoc = XDocument.Load(path);
var root = xdoc.Element("emailTemplate");
this.Subject = root.Element("subject").Value;
this.Body = root.Element("body").Value;
}
public string GenerateBody(T data){
return Generate(this.Body, data); 
}
public string GenerateSubject(T data){
return Generate(this.Subject, data);
}
private static string Generate(string source, T data){
// coming shortly
}
}

 

So now we have our class stubbed up, the constructor takes a path to an XML document and then we use a XDocument object to traverse into our XML and find the subject and body. The Subject and Body properties are made with private setters so that you can't edit the subject accidentally. Also, so that you can reuse the current loaded template the "Generate" methods will return a string rather than replacing the contents of the current object.

Step 3, writing the Generator

This is where the fun bit comes in, we're going to use Reflection to find all the properties of our Generic class and then write it to a source.

private static string Generate(string source, T data){
Type theType = data.GetType();
PropertyInfo[] properties = theType.GetProperties();
properties.ForEach(p => result = result.Replace("{{ " + p.Name + " }}", p.GetValue(data, new Object[0]).ToString()));
return result;
}

 

So to sum up I'm using Reflection to get all the properties from the object and then using the ForEach extension method (if you don't have the ForEach extension method check it out here). So for each of the properties I'll create a token (which I'm using in the form of "{{ MyProeprty }}") and then do a replace.
I've found this template to be really effective as it'll allow for easy adding of new properties to my object without having to re-write the generation method, and it doesn't give a damn whether the property actually exists.

I can quite easily use a LINQ to SQL expression like this:

var myItems = ctx.MyDataItems.Select(m => new { Property1 = m.Property1, Property2 = m.Property2 });

And pass it straight in. Got to love anonymous types!