Tuesday, September 06, 2011

Razor Engine, more than just web pages

Will working with a pet project I cam 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.

 

Classes

 

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://lukencode.com/2011/04/30/fluent-email-now-supporting-razor-syntax-for-templates/



 



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:



 



Template



 



and the code looks something like this (remembering our DRY principle Winking smile)



 



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();
}

public string ParseFromFile<T>(string fileName, T model){
const string tilde = "~";
if (fileName.StartsWith(tilde)) {
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
fileName = Path.GetFullPath(baseDir + fileName.Replace(tilde, String.Empty));
}

var path = Path.GetFullPath(fileName);
var template = String.Empty;

using (var textReader = new StreamReader(path)){
template = textReader.ReadToEnd();
}

return ParseFromString(template, model);
}

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
private static void InitializeRazorParser(){
dynamic temp = new ExpandoObject();
temp.PlaceHolder = String.Empty;
}
}


 



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