Friday 29 May 2009

Creating HTML using NVelocity

I recently had need to create some HTML output from a .NET console application. Often in this scenario, I will simply crank out the HTML in code, constructing it bit by bit with a StringBuilder. However, this time round I decided to look for a more elegant solution. I wanted to create a text file with a template, and for my data to be dynamically put into the right place.

While this could be done with XLST, or even some custom string replacement code, I decided to try out a .NET templating engine. There are a number of these available, including NHaml, Brail, and Spark, but I chose to go with NVelocity, whose syntax seemed to be nice and straightforward, allowing other developers to easily see what is going on and make changes to the templates.

Getting the NVelocity DLL

This proved harder than I was expecting. The original NVelocity project has not been updated in several years, but over at the Castle Project they have taken the source and are improving it. However, I couldn’t find a Castle Project download that contained a built DLL, so I ended up having to download the entire Castle Project source using Subversion, and building it.

Creating a Template

This is the nice and easy bit. Here you can see I am printing out a HTML table of books in a collection of books. I think the NVelocity syntax is pretty self-explanatory.

<h3>Books</h3>

#foreach($book in $books)
#beforeall
<table>
  <tr>
    <th>Title</th>
    <th>Author</th>
  </tr>
#before
  <tr>
#each
    <td>$book.Title</td>
    <td>$book.Author</td>
#after
  </tr>
#afterall
</table>
#nodata
No books found.
#end

Applying the Transformation

Now we need to get hold of the template we created and load it into a stream. I embedded my template as a resource. Then we need to set up a VelocityContext, which will contain all the data needed to be injected into our HTML. Then it is a simple matter of creating the VelocityEngine and passing it the context and the template. It returns a string, which can be written to disk if required.

public static string TransformBooksToHtml(IEnumerable<Book> books, string resourceTemplateName)
{
    Stream templateStream = typeof(TemplateEngine).Assembly.GetManifestResourceStream(resourceTemplateName);
    var context = new VelocityContext();
    context.Put("books", books);
    return ApplyTemplate(templateStream, context);
}

public static string ApplyTemplate(Stream templateStream, VelocityContext context)
{
    VelocityEngine velocity = new VelocityEngine();
    ExtendedProperties props = new ExtendedProperties();
    velocity.Init(props);
    var writer = new StringWriter();
    velocity.Evaluate(context, writer, "XYZ", new StreamReader(templateStream));
    return writer.GetStringBuilder().ToString();
}

Limitations

One limitation that comes to mind is that I am not sure what would happen if the data contained characters that needed to be encoded for HTML (e.g. the less than symbol). I haven’t tested this scenario, but I am sure there is some way of working round it (especially as NVelocity is intended specifically for scenarios requiring HTML output).