Will working with a pet project I came across the need to populate a standard message with certain data values. Enter templating.
First thing anyone might do is add the templating mechanism to the message distribution stack. I take a slightly different stance on this. I believe the message distribution stack should have no knowledge of the templates being used. All it should effectively do is distribute the formatted message according to the specified communication channel.
This means that the piece of code creating the message would have to assign the formatted body to the message object. Something like this:
Message myMessage = new Message{
From = "from@domain.com",
To = "to@domain.com",
Subject = "My Subject",
Body = "This is where my super long message that needs to be formatted will go"
};
MessageGatewayFactory.CreateGatewayInstance(MessageType.Email).SendMessage(myMessage);
As you can see the Body is is looking a bit smelly. This could be rectified by using an external resource. Good idea! The problem is that we might (well probably) will have to add dynamic data to the body.
So I started researching some templating solutions. There are some really heavy weight solutions out there. I didn’t want anything heavy weight though and I wanted to use the Razor Engine. My travels led me to a project called Fluent Email. A write up of the project can be found here http://ping.fm/q4umY
When running through the examples and having a look at the code I noticed that it did everything I needed it to do but not in the fashion I wanted it done. Don’t get me wrong, this project has a great deal of potential and will prove very useful to many projects, it just wasn’t exactly what I was looking for. Digging a little deeper into the source I found the Email class which contained a method to parse a Razor formatted string template and a method to read a Razor formatted file of the disk. BINGO!
So I extracted the two methods and went ahead and changed them accordingly and lined them up with some best practises. This is what came out:
and the code looks something like this (remembering our DRY principle
)
Our Interface definition:
public interface ITemplateParser{
string ParseFromFile<T>(string fileName, T model);
string ParseFromString<T>(string template, T model);
}
Parser factory:
public static class ParserFactory {
public static ITemplateParser TemplateParser {
get { return new RazorParserImpl(); }
}
}Parser implementation:
class RazorParserImpl : ITemplateParser {
public RazorParserImpl(){
InitializeRazorParser();
}
/// <summary>
/// Parses from file.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="fileName">Name of the file.</param>
/// <param name="model">The model.</param>
/// <returns></returns>
public string ParseFromFile<T>(string fileName, T model){
var path = GetPath(fileName);
using (var textReader = new StreamReader(path)){
var template = textReader.ReadToEnd();
return ParseFromString(template, model);
}
}
/// <summary>
/// Parses from string.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="template">The template.</param>
/// <param name="model">The model.</param>
/// <returns></returns>
public string ParseFromString<T>(string template, T model){
var result = Razor.Parse(template, model);
return result;
}
//Some weirdness pointed out by lukencode. Will be validating this further when I get a chance
/// <summary>
/// Initializes the razor parser.
/// </summary>
private static void InitializeRazorParser(){
dynamic temp = new ExpandoObject();
temp.PlaceHolder = String.Empty;
}
/// <summary>
/// Gets the path.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <returns></returns>
private static String GetPath(string fileName){
const string tilde = "~";
string output;
if (fileName.StartsWith(tilde)) {
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
output = Path.GetFullPath(baseDir + fileName.Replace(tilde, String.Empty));
} else{
output = Path.GetFullPath(fileName);
}
return output;
}
}
Usage
//Previous look ups left for brevity
var body = ParserFactory.TemplateParser.ParseFromFile("~/Templates/MyTemplateFile.cshtml", response.Profile);
var response = messageManager.SendMessage(new MessageRequest {
Body = body,
MessageType = MessageType.SayHello,
Subject = "Just popped in to say hello",
ToId = id
});
I decided to define the template parser as an interface to allow expansion on parser and templating engines at a later stage. When this comes about, obviously I will have to change the factory method to return the correct implementation. Yes it might be over kill for now but lose coupling is something I try do from the beginning.
I did submit these changes to the project in case you where wondering. When testing it in the scenarios I needed it in it worked really nicely. I really big thanks to the original author!
Hope you find it useful as well.
References:
No comments:
Post a Comment