views:

476

answers:

3

I'm looking to use NVelocity in my ASP.NET MVC application, not as a view engine, just for rendering some email templates.

However, I cannot for the life of me get it to work. I have downloaded it from the castle project and followed the example at http://www.castleproject.org/others/nvelocity/usingit.html#step1

No matter what I try I don't seem to be able to load a template located in my site. The example suggests using the absolute path, which I have tried to no avail:

Template t = engine.GetTemplate("/Templates/TestEmail.vm");

So please can someone give me two examples. One of loading a template located in the web site directory and secondly one parsing a string variable (as it is likely that my templates will be stored in a database).

Many thanks Ben

+2  A: 

I've used this class in one of my past projects:

public interface ITemplateRepository
{
    string RenderTemplate(string templateName, IDictionary<string, object> data);
    string RenderTemplate(string masterPage, string templateName, IDictionary<string, object> data);
}

public class NVelocityTemplateRepository : ITemplateRepository
{
    private readonly string _templatesPath;

    public NVelocityTemplateRepository(string templatesPath)
    {
        _templatesPath = templatesPath;
    }

    public string RenderTemplate(string templateName, IDictionary<string, object> data)
    {
        return RenderTemplate(null, templateName, data);
    }

    public string RenderTemplate(string masterPage, string templateName, IDictionary<string, object> data)
    {
        if (string.IsNullOrEmpty(templateName))
        {
            throw new ArgumentException("The \"templateName\" parameter must be specified", "templateName");
        }

        var name = !string.IsNullOrEmpty(masterPage)
            ? masterPage : templateName;

        var engine = new VelocityEngine();
        var props = new ExtendedProperties();
        props.AddProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, _templatesPath);
        engine.Init(props);
        var template = engine.GetTemplate(name);
        template.Encoding = Encoding.UTF8.BodyName;
        var context = new VelocityContext();

        var templateData = data ?? new Dictionary<string, object>();
        foreach (var key in templateData.Keys)
        {
            context.Put(key, templateData[key]);
        }

        if (!string.IsNullOrEmpty(masterPage))
        {
            context.Put("childContent", templateName);
        }

        using (var writer = new StringWriter())
        {
            engine.MergeTemplate(name, context, writer);
            return writer.GetStringBuilder().ToString();
        }
    }
}

In order to instantiate the NVelocityTemplateRepository class you need to provide an absolute path where your templates root is. Then you use relative paths to reference your vm files.

Darin Dimitrov
@Darin, I tried your example but still get the same error when trying to load the template file. Perhaps I am passing my paths in incorrectly. The template in question is ~/Content/Templates/TestEmail.vm and my code is below: var templatesPath = VirtualPathUtility.ToAbsolute("~/Content/templates"); ITemplateService templateService = new NVelocityTemplateService(templatesPath); var objs = new Dictionary<string, object>(); return templateService.RenderTemplate("TestEmail.vm", objs);
Ben
VirtualPathUtility does not return absolute path. You need to pass an absolute path such as `c:\mytemplates`. Try this instead: `Path.Combine(HostingEnvironment.ApplicationPhysicalPath, @"Content\templates")`.
Darin Dimitrov
I changed to Server.MapPath to get physical directory on server. Now works a treat. Thanks.
Ben
@Ben, glad to hear you made it work.
Darin Dimitrov
A: 

I also added the following method to process a string instead of a template file (say if retrieving the template content from a database):

        public string RenderTemplateContent(string templateContent, IDictionary<string, object> data)
    {
        if (string.IsNullOrEmpty(templateContent))
            throw new ArgumentException("Template content cannot be null", "templateContent");

        var engine = new VelocityEngine();
        engine.Init();

        var context = GetContext(data);

        using (var writer = new StringWriter()) {
            engine.Evaluate(context, writer, "", templateContent);
            return writer.GetStringBuilder().ToString();
        }
    }

And used StructureMap to initialize the service:

            ForRequestedType<ITemplateService>()
            .TheDefault.Is.ConstructedBy(()=> 
                new NVelocityTemplateService(HttpContext.Current.Server.MapPath("~/Content/Templates/")));
Ben
+1  A: 

You might find the TemplateEngine component useful.

It's an abstraction over template engines with a NVelocity implementation, similar to Darin's answer, but it should perform marginally better since it uses a single instance of the VelocityEngine (as opposed to initializing one instance per render) and has optional caching. It also has a couple other features, like logging, NVelocity property overriding and loading templates from assembly resources.

Mauricio Scheffer
Thanks - this was useful.
Ben