views:

377

answers:

5

I'm having some difficulty with using NVelocity in an ASP.NET MVC application. I'm using it as a way of generating emails.

As far as I can make out the details I'm passing are all correct, but it fails to load the template.

Here is the code:

private const string defaultTemplatePath = "Views\\EmailTemplates\\";

...

velocityEngine = new VelocityEngine();
basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, defaultTemplatePath);
ExtendedProperties properties = new ExtendedProperties();
properties.Add(RuntimeConstants.RESOURCE_LOADER, "file");
properties.Add(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, basePath);
velocityEngine.Init(properties);

The basePath is the correct directory, I've pasted the value into explorer to ensure it is correct.

if (!velocityEngine.TemplateExists(name))
    throw new InvalidOperationException(string.Format("Could not find a template named '{0}'", name));


Template result = velocityEngine.GetTemplate(name);

'name' above is a valid filename in the folder defined as basePath above. However, TemplateExists returns false. If I comment that conditional out and let it fail on the GetTemplate method call the stack trace looks like this:

   at NVelocity.Runtime.Resource.ResourceManagerImpl.LoadResource(String resourceName, ResourceType resourceType, String encoding)
   at NVelocity.Runtime.Resource.ResourceManagerImpl.GetResource(String resourceName, ResourceType resourceType, String encoding)
   at NVelocity.Runtime.RuntimeInstance.GetTemplate(String name, String encoding)
   at NVelocity.Runtime.RuntimeInstance.GetTemplate(String name)
   at NVelocity.App.VelocityEngine.GetTemplate(String name)
...

I'm now at a bit of an impasse. I feel that the answer is blindingly obvious, but I just can't seem to see it at the moment.

+1  A: 

Okay - So I'm managed to get something working but it is a bit of a hack and isn't anywhere near a solution that I want, but it got something working.

Basically, I manually load in the template into a string then pass that string to the velocityEngine.Evaluate() method which writes the result into the the given StringWriter. The side effect of this is that the #parse instructions in the template don't work because it still cannot find the files.

using (StringWriter writer = new StringWriter())
{
    velocityEngine.Evaluate(context, writer, templateName, template);
    return writer.ToString();
}

In the code above templateName is irrelevant as it isn't used. template is the string that contains the entire template that has been pre-loaded from disk.

I'd still appreciate any better solutions as I really don't like this.

Colin Mackay
Right, this is the way to use the underlying engine as Text only. THose Java guys, crazy ideas... It's why Castle created that NVelocityTemplateEngine project - it's a bunch of abstraction around exactly what you posted here.
eduncan911
A: 

The tests are the ultimate authority:

http://fisheye2.atlassian.com/browse/castleproject/NVelocity/trunk/src/NVelocity.Tests/Test/ParserTest.cs?r=6005#l122

Or you could use the TemplateEngine component which is a thin wrapper around NVelocity that makes things easier.

Mauricio Scheffer
+3  A: 

Hey Colin,

Have you considered using Castle's NVelocityTemplateEngine?

Download from the "TemplateEngine Component 1.1 - September 29th, 2009" section and reference the following assemblies:

using Castle.Components.Common.TemplateEngine.NVelocityTemplateEngine;
using Castle.Components.Common.TemplateEngine;

Then you can simply call:

using (var writer = new StringWriter())
{
    _templateEngine.Process(data, string.Empty, writer, _templateContents);
    return writer.ToString();
}

Where:

  • _templateEngine is your NVelocityTemplateEngine
  • data is your Dictionary of information (I'm using a Dictionary to enable me to access objects by a key ($objectKeyName) in my template.
  • _templateContents is the actual template string itself.

I hope this is of help to you!

Just to add, you'll want to put that into a static method returning a string of course!

Robert Reid
+1  A: 

Try setting the file.resource.loader.path

http://weblogs.asp.net/george_v_reilly/archive/2007/03/06/img-srchttpwwwcodegenerationnetlogosnveloc.aspx

Scozzard
+1  A: 

Had this issue recently - NVelocity needs to be initialised with the location of the template files. In this case mergeValues is an anonymous type so in my template I can just refer to $Values.SomeItem:

    private string Merge(Object mergeValues)
    {
        var velocity = new VelocityEngine();
        var props = new ExtendedProperties();
        props.AddProperty("file.resource.loader.path", @"D:\Path\To\Templates");
        velocity.Init(props);
        var template = velocity.GetTemplate("MailTemplate.vm");
        var context = new VelocityContext();
        context.Put("Values", mergeValues);

        using (var writer = new StringWriter())
        {
            template.Merge(context, writer);
            return writer.ToString();
        }
    }
David Clarke
This worked perfectly for me. Thanks
scottm