Home » NVelocity » Kicking It Into Overdrive With NVelocity

Kicking It Into Overdrive With NVelocity

Sitecore Technology MVP 2016
Sitecore MVP 2015
Sitecore MVP 2014

Enter your email address to follow this blog and receive notifications of new posts by email.

Tweets

A few weeks ago, I was tasked with improving one of our Healthcare Specific Website Modules, and felt compelled to share how I used NVelocity to replace tokens set in a Rich Text field — $name and $date are examples of Sitecore tokens. But, before I dive into some code, I will briefly touch upon what NVelocity is.

What exactly is NVelocity? NVelocity is a .NET template engine for replacing string tokens with code references. In other words, NVelocity is a framework that will replace tokens with values returned from code snippets given to the template engine.

There a many examples of using NVelocity in Sitecore across the web. Sitecore provides one such example in this article. This article — although a bit dated since it was written for Sitecore v5.1 — provides code examples that could still be used today.

Alistair Deneys also wrote a good article about NVelocity here. Deneys posits Sitecore no longer uses NVelocity for token replacement. However, three years after Deneys’ penned his article, code within Sitecore.Kernel still references it. Here is an example of it being used within Sitecore.Kernel (v6.5.0 rev. 111123):

Sitecore has created its own API around NVelocity. Sitecore’s wrapper API is defined within Sitecore.NVelocity.dll. In order to use the following code, you must reference this assembly.

Now, let’s get our hands dirty with some code.

First, I created an adapter to encapsulate Sitecore’s static Velocity engine object for doing token replacements.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

using NVelocity.Context;

namespace Sitecore.Sandbox.Utilities.StringUtilities.Base
{
    public interface ITokenContextEvaluator
    {
        bool Evaluate(IContext context, TextWriter writer, string logTag, Stream instream);

        bool Evaluate(IContext context, TextWriter out_Renamed, string logTag, string instring);

        bool Evaluate(IContext context, TextWriter writer, string logTag, TextReader reader);
    }
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

using NVelocity.App;
using NVelocity.Context;

using Sitecore.Sandbox.Utilities.StringUtilities.Base;

namespace Sitecore.Sandbox.Utilities.StringUtilities
{
    public class TokenContextEvaluator : ITokenContextEvaluator
    {
        private TokenContextEvaluator()
        {
            Initailize();
        }

        private void Initailize()
        {
            Velocity.Init();
        }

        public bool Evaluate(IContext context, TextWriter writer, string logTag, Stream instream)
        {
            return Velocity.Evaluate(context, writer, logTag, instream);
        }

        public bool Evaluate(IContext context, TextWriter out_Renamed, string logTag, string instring)
        {
            return Velocity.Evaluate(context, out_Renamed, logTag, instring);
        }

        public bool Evaluate(IContext context, TextWriter writer, string logTag, TextReader reader)
        {
            return Velocity.Evaluate(context, writer, logTag, reader);
        }

        public static ITokenContextEvaluator CreateNewTokenContextEvaluator()
        {
            return new TokenContextEvaluator();
        }
    }
}

Next, I created a class that defines an one-to-one mapping between a string token and an object reference — the value of the code given to the template engine.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Utilities.StringUtilities.DTO
{
    public class TokenKeyValue
    {
        public string Key { get; private set; }
        public object Value { get; private set; }

        public TokenKeyValue(string key, object value)
        {
            SetKey(key);
            SetValue(value);
        }

        private void SetKey(string key)
        {
            Assert.ArgumentNotNullOrEmpty(key, "key");
            Key = key;
        }

        private void SetValue(object value)
        {
            Value = value;
        }
    }
}

Then I created a Data transfer object to pass an instance of the adapter defined above, a collection of token mappings and an instance of NVelocity’s IContext to the main Tokenator class below — all this being done this way to allow for dependency injection.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NVelocity.Context;

using Sitecore.Sandbox.Utilities.StringUtilities.Base;

namespace Sitecore.Sandbox.Utilities.StringUtilities.DTO
{
    public class TokenatorSettings
    {
        public ITokenContextEvaluator TokenContextEvaluator { get; set; }

        public IContext TokenContext { get; set; }

        public IEnumerable<TokenKeyValue> TokenKeyValues { get; set; }

        public TokenatorSettings()
        {
        }
    }
}

With all the pieces above floating around, it’s now time to glue them all together in the main Tokenator class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sitecore.Sandbox.Utilities.StringUtilities.Base
{
    public interface ITokenator
    {
        string ReplaceTokens(string value);
    }
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

using Sitecore.Diagnostics;
using Sitecore.Text.NVelocity;

using NVelocity;
using NVelocity.App;
using NVelocity.Context;

using Sitecore.Sandbox.Utilities.StringUtilities.Base;
using Sitecore.Sandbox.Utilities.StringUtilities.DTO;

namespace Sitecore.Sandbox.Utilities.StringUtilities
{
    public class Tokenator : ITokenator
    {
        private const string LogName = "Tokenator Replacement Action";

        private TokenatorSettings TokenatorSettings { get; set; }

        private Tokenator(IEnumerable<TokenKeyValue> tokenKeyValues)
            : this(CreateNewTokenatorSettingsWithDefaults(tokenKeyValues))
        {
        }

        private Tokenator(TokenatorSettings tokenatorSettings)
        {
            SetTokenatorSettings(tokenatorSettings);
            Initialize();
        }

        private void SetTokenatorSettings(TokenatorSettings tokenatorSettings)
        {
            AssertTokenatorSettings(tokenatorSettings);
            TokenatorSettings = tokenatorSettings;
        }

        private void AssertTokenatorSettings(TokenatorSettings tokenatorSettings)
        {
            Assert.ArgumentNotNull(tokenatorSettings, "tokenatorSettings");
            Assert.ArgumentNotNull(tokenatorSettings.TokenContextEvaluator, "tokenatorSettings.TokenContextEvaluator");
            Assert.ArgumentNotNull(tokenatorSettings.TokenContext, "tokenatorSettings.TokenContext");
            Assert.ArgumentNotNull(tokenatorSettings.TokenKeyValues, "tokenatorSettings.TokenKeyValues");
        }

        private void Initialize()
        {
            foreach (TokenKeyValue tokenKeyValue in TokenatorSettings.TokenKeyValues)
            {
                TokenatorSettings.TokenContext.Put(tokenKeyValue.Key, tokenKeyValue.Value);
            }
        }

        public string ReplaceTokens(string value)
        {
            string tokensReplaced = string.Empty;

            using (StringWriter stringWriter = new StringWriter())
            {
                TokenatorSettings.TokenContextEvaluator.Evaluate(TokenatorSettings.TokenContext, stringWriter, LogName, value);
                tokensReplaced = stringWriter.ToString();
            }

            return tokensReplaced;
        }

        private void LogError(Exception exception)
        {
            Log.Error(this.ToString(), exception, this);
        }

        private static TokenatorSettings CreateNewTokenatorSettingsWithDefaults(IEnumerable<TokenKeyValue> tokenKeyValues)
        {
            return new TokenatorSettings
            {
                TokenContextEvaluator = GetDefaultTokenContextEvaluator(),
                TokenContext = GetDefaultTokenContext(),
                TokenKeyValues = tokenKeyValues
            };
        }

        private static ITokenContextEvaluator GetDefaultTokenContextEvaluator()
        {
            return StringUtilities.TokenContextEvaluator.CreateNewTokenContextEvaluator();
        }

        private static IContext GetDefaultTokenContext()
        {
            return new VelocityContext();
        }

        public static ITokenator CreateNewTokenator(IEnumerable<TokenKeyValue> tokenKeyValues)
        {
            return new Tokenator(tokenKeyValues);
        }

        public static ITokenator CreateNewTokenator(TokenatorSettings tokenatorSettings)
        {
            return new Tokenator(tokenatorSettings);
        }
    }
}

The following sublayout code was used as a test harness for my Tokenator object, and to illustrate how client code would use it.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Tokenator Test.ascx.cs" Inherits="Sitecore650rev120706.layouts.sublayouts.Tokenator_Test" %>
<asp:Literal ID="litTokenatorTest" runat="server" />
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using Sitecore.Sandbox.Utilities.StringUtilities;
using Sitecore.Sandbox.Utilities.StringUtilities.Base;
using Sitecore.Sandbox.Utilities.StringUtilities.DTO;

namespace Sitecore650rev120706.layouts.sublayouts
{
    public partial class Tokenator_Test : System.Web.UI.UserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            SetLiteralText();
        }

        private void SetLiteralText()
        {
            litTokenatorTest.Text = GetTokenReplacedContent();
        }

        private string GetTokenReplacedContent()
        {
            string content = Sitecore.Context.Item["Text"];
            return ReplaceTokens(content);
        }

        private string ReplaceTokens(string content)
        {
            IEnumerable<TokenKeyValue> tokenKeyValues = GetTokenKeyValues();
            ITokenator tokenator = Tokenator.CreateNewTokenator(tokenKeyValues);
            return tokenator.ReplaceTokens(content);
        }

        private IEnumerable<TokenKeyValue> GetTokenKeyValues()
        {
            IList<TokenKeyValue> tokenKeyValues = new List<TokenKeyValue>();
            tokenKeyValues.Add(new TokenKeyValue("localtime", DateTime.Now));
            tokenKeyValues.Add(new TokenKeyValue("developer", "@mike_i_reynolds"));
            tokenKeyValues.Add(new TokenKeyValue("random", new Random().Next(10000)));
            return tokenKeyValues;
        }
    }
}

Token content in a Rich Text field:

Test output:

NVelocity is definitely a great tool to tap into and harness — especially to make content more dynamic.

However, if there is one takeaway I would like for you the reader to part with, it would be this: stay relentlessly curious, and keep on digging through the treasure troves in Sitecore’s assemblies. This article would not have been possible if I did not have a copy of .NET Reflector, Sitecore.Kernel.dll, Sitecore.NVelocity.dll and an unquenchable thirst for learning.

Happy coding!

Advertisements

1 Comment

  1. […] post shows how I did just that, and used NVelocity via a utility class I had built for my article discussing […]

Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: