Click here to Skip to main content
15,867,330 members
Articles / Web Development / ASP.NET

Template merging with NVelocity and ASP.NET

Rate me:
Please Sign up or sign in to vote.
4.62/5 (31 votes)
24 Aug 2007CPOL7 min read 210.3K   4.1K   80   48
A tutorial about merging templates with NVelocity and ASP.NET.

Introduction

If you have ever tried to implement a newsletter system or a service for communicating with website users, you have probably faced the requisite to send multiple email messages with a common template and some differences, like a personalized greeting in the message header, or an unsubscribe link with a personalized querystring.

Maybe the first way to accomplish this is by writing the static parts of the message in plain text files - for example: emailheader.txt, emailfooter.txt - and reassemble them in the code, merging the varying parts, like recipient's name, surname, personalized querystring link, and so on.

But what about having a template engine which, given a template - be it a file on the file system, an assembly embedded resource, or an in-memory object - and the information we want to merge in the template, does this all for us?

"NVelocity[^] is a .NET-based template engine. It permits anyone to use the simple yet powerful template language to reference objects defined in .NET code. The purpose of this project is to port the Jakarta Velocity[^] project to Microsoft .NET (written in C#)."

Going back to the newsletter example, using NVelocity, the developer just needs to:

  • create a template for email messages specifying the parts that will have to be merged using a simple language called VTL (Velocity Template Language).
  • supply the information to be merged.

NVelocity will do the rest.

Background

In order to write NVelocity templates, a basic knowledge of the language it uses would be helpful. Anyway, in email templates, it's rarely necessary to write loops, methods, or branch conditions; instead, more often, the need is to replace single variables with values provided programmatically.

In this article, I'm going to show a simple way of writing templates, but if you think that you need something more complex, my advice is to read the VTL Reference Guide[^].

The notation for variables, as taken from the VTL Reference guide, is the following:

Notation (variable name):

$ [ ! ][ { ][ a..z, A..Z ][ a..z, A..Z, 0..9, -, _ ][ } ]

Examples:

  • Normal notation: $mud-Slinger_9
  • Silent notation: $!mud-Slinger_9
  • Formal notation: ${mud-Slinger_9}

About the article

In this article, I am going to show how I implemented a wrapper for NVelocity functionalities, which simplifies merging templates coming from a file system, assembly resources, or in-memory objects, and will build a simple web project for demonstration. The source code zip file contains the wrapper project, while the sample project zip file contains a web project which shows how to use it to merge templates in ASP.NET.

As a note, keep in mind that the wrapper can be used in any other project type, from console applications to Windows Forms and WebServices as well. I have chosen to build the demo in ASP.NET because it is a good compromise between a nice user interface and ease of creation.

Using the code

The main access point is the class NVelocityEngineFactory, which exposes three static methods. Each of these methods return an object that implements the INVelocityEngine interface, which can respectively be used to retrieve templates from embedded resources, file system, or memory. The boolean parameter required by each of the methods is used to specify whether the result of the merge has to be cached or not.

The three correspondent engine types are NVelocityAssemblyEngine, NVelocityFileEngine, and NVelocityMemoryEngine.

All of them inherit from the NVelocityEngineBase base class, which defines a common constructor and a static protected method.

Since implementing the INVelocityEngine interface, the three engine classes expose a public method with an overload called Process, which take two or three parameters and return a string or void.

In the first case, the two input parameters are the IDictionary object containing the values to be merged in the template and the template itself, while the return value is the merged template put in a string object.

In the second case, the additional parameter is a TextWriter object which, after the processing, will contain the merged template.

The template

To use the three types of engines, we need to create a template to be merged. Using VTL syntax, we write a simple template which can be used to render a message containing a simple personalized greeting, along with the date when the merging was done.

To test all of the three engines, the demo project contains a template file placed on the file system, a template file embedded in the assembly as a resource, and an in-memory template (a string) created at runtime.

To write a template file, we just need to open a text editor and save the file with the .vm extension.

Hi $name $surname, this output has been generated from a $templateType template on $date.

This is a very simple template, but it's not difficult to imagine in its place an entire HTML email with nice formatting and some images.

Using the wrapper along with the templates

Now, all we need to do is:

  • Create new instances of the template engines, supplying the information about the template. In order to process a file template, the directory of the template is needed, while processing an embedded resource template will require the assembly name. Finally, to process an in-memory object, no specific parameter is needed, the template will be supplied when the Process method of the engine is called.
  • Add some contents to an object implementing the IDictionary interface and pass it to the engine Process method.

File template

Supposing we have placed the template file in a subdirectory called Templates in the root folder of our web project and named it SimpleTemplate.vm:

C#
string templateDir = HttpContext.Current.Server.MapPath("Templates");
string templateName = "SimpleTemplate.vm";

INVelocityEngine fileEngine = 
    NVelocityEngineFactory.CreateNVelocityFileEngine(templateDir, true);

IDictionary context = new Hashtable();

context.Add("name", TextBox1.Text);
context.Add("surname", TextBox2.Text);
context.Add("templateType", "file");
context.Add("date", DateTime.Now.ToString("D"));

LabelMergedFile.Text = fileEngine.Process(context, templateName);

The LabelMergedFile Text attribute will contain the text of the template and, in place of the variables, the values supplied in the code.

Embedded resource template

Supposing we have placed the template file in a subdirectory called EmbeddedResources in the root folder of our web project, named it SimpleTemplate.vm, and marked it as an embedded resource (under Visual Studio, this can be accomplished by right clicking the file, choosing Properties, and then setting the "Build Action" property to "Embedded Resource"):

C#
string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;

INVelocityEngine embeddedEngine = 
    NVelocityEngineFactory.CreateNVelocityAssemblyEngine(assemblyName, true);

IDictionary context = new Hashtable();

context.Add("name", TextBox1.Text);
context.Add("surname", TextBox2.Text);
context.Add("templateType", "embedded");
context.Add("date", DateTime.Now.ToString("D"));

LabelMergedEmbedded.Text = 
    embeddedEngine.Process(context, "EmbeddedResources.SimpleTemplate.vm"); 

Note that when calling the Process method, you need to specify the path of the resource, like in file system paths, but with dots in place of slashes.

In-memory template

Differently from the previous cases, now the template must reside in memory, so we create a string object that contains the template:

C#
string template = "Hi $name $surname, this output has been " + 
                  "generated from a $templateType template on $date.";

INVelocityEngine memoryEngine =
    NVelocityEngineFactory.CreateNVelocityMemoryEngine(true);

IDictionary context = new Hashtable();

context.Add("name", TextBox1.Text);
context.Add("surname", TextBox2.Text);
context.Add("templateType", "memory");
context.Add("date", DateTime.Now.ToString("D"));

LabelMergedMemory.Text = memoryEngine.Process(context, template);

Note that the keys of the objects you place in the IDictionary object must correspond to the variable's names specified in the template if you want NVelocity to merge them. In case you forget to supply a key whose corresponding variable is contained in the template, NVelocity just forgets about it and will give in output $variablename.

Points of interest

I wrote this brief tutorial because I myself have been looking for something like this while I was implementing a newsletter service and after getting crazy at trying to merge the templates by hand.

NVelocity can do much more than this anyway, and if you want to find a place where NVelocity is truly "milked", you should take a look at CastleProject[^], where they use it as a view engine in their MonoRail[^] project.

The sad thing about NVelocity is that the project is almost dead; the latest release, 0.4.2, is dated October 27, 2003.

The positive thing is that CastleProject developers have made a fork of the project, fixed some bugs, and killed a tedious assembly dependence on an old Log4Net distribution, which NVelocity used as the internal logger engine.

Actually, this NVelocity fork has reached version 0.5, which is the one I have included in the sample project, and whose source can be found in CastleProject's SVN repository.

Revision history

  • 09 Feb 2006: Added support for embedded and in-memory templates.
  • 17 Jan 2006: Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralA suggestion for a helpful tip in the article Pin
dessus24-Aug-07 14:15
dessus24-Aug-07 14:15 
GeneralRe: A suggestion for a helpful tip in the article Pin
Simone Busoli24-Aug-07 16:30
Simone Busoli24-Aug-07 16:30 
QuestionCan this support nested block? Pin
=Angie=15-Mar-07 10:19
=Angie=15-Mar-07 10:19 
AnswerRe: Can this support nested block? Pin
Simone Busoli15-Mar-07 11:06
Simone Busoli15-Mar-07 11:06 
GeneralUTF-8 issues & minor fix Pin
ShanPlourde25-Feb-07 16:14
ShanPlourde25-Feb-07 16:14 
GeneralRe: UTF-8 issues & minor fix Pin
Simone Busoli26-Feb-07 0:28
Simone Busoli26-Feb-07 0:28 
AnswerFIXED: UTF-8 issues & minor fix Pin
Simone Busoli9-Mar-07 3:21
Simone Busoli9-Mar-07 3:21 
QuestionNice in memory, but what about $parse? Pin
shawthing5-Oct-06 10:27
shawthing5-Oct-06 10:27 
I'm just implementing an nvelocity wrapper myself, with templates coming from the db. I have classes to handle the db part, so i was interested in your inmemory version - but surely this won't work with $parse, right?

In other words, by using the default resource loader (file) any $parse templates will still be loaded from disk. I'm trying to implement a new DBLoader resource loader but it's still moaning when trying to instantiate. But do you agree that creating a new loader is the best way?
AnswerRe: Nice in memory, but what about $parse? Pin
Simone Busoli5-Oct-06 15:06
Simone Busoli5-Oct-06 15:06 
GeneralNice Pin
rperetz9-Aug-06 4:37
rperetz9-Aug-06 4:37 
GeneralRe: Nice Pin
Simone Busoli9-Aug-06 4:51
Simone Busoli9-Aug-06 4:51 
GeneralMany Thanks. Pin
Vikram Saxena9-Jul-06 1:54
Vikram Saxena9-Jul-06 1:54 
GeneralRe: Many Thanks. Pin
Simone Busoli9-Jul-06 2:10
Simone Busoli9-Jul-06 2:10 
QuestionI don't understand why this is rated so low. Excellent article. Pin
aaava27-Jun-06 4:37
aaava27-Jun-06 4:37 
AnswerRe: I don't understand why this is rated so low. Excellent article. Pin
Simone Busoli27-Jun-06 5:43
Simone Busoli27-Jun-06 5:43 
NewsHere comes the solution Pin
Simone Busoli27-Jun-06 13:51
Simone Busoli27-Jun-06 13:51 
GeneralRe: Here comes the solution Pin
aaava2-Jul-06 9:46
aaava2-Jul-06 9:46 
GeneralRe: Here comes the solution Pin
aaava2-Jul-06 9:49
aaava2-Jul-06 9:49 
GeneralRe: Here comes the solution Pin
Simone Busoli2-Jul-06 13:33
Simone Busoli2-Jul-06 13:33 
GeneralAdvantages over XSLT !! Pin
Esam Salah26-Jan-06 2:20
Esam Salah26-Jan-06 2:20 
GeneralRe: Advantages over XSLT !! Pin
Simone Busoli26-Jan-06 2:32
Simone Busoli26-Jan-06 2:32 
GeneralRe: Advantages over XSLT !! Pin
attackweasel10-Feb-06 4:16
attackweasel10-Feb-06 4:16 
GeneralRe: Advantages over XSLT !! Pin
Simone Busoli12-Feb-06 5:51
Simone Busoli12-Feb-06 5:51 
GeneralNice Pin
David Kemp19-Jan-06 1:40
David Kemp19-Jan-06 1:40 
JokeRe: Nice Pin
Simone Busoli19-Jan-06 2:44
Simone Busoli19-Jan-06 2:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.