Home » NVelocity

Category Archives: NVelocity

Content Manage Custom Standard Values Tokens in the Sitecore Client

A few days back, John West — Chief Technology Officer at Sitecore USA — blogged about adding custom tokens in a subclass of Sitecore.Data.MasterVariablesReplacer.

One thing that surprised me was how his solution did not use NVelocity, albeit I discovered why: the class Sitecore.Data.MasterVariablesReplacer does not use it, and as John states in this tweet, using NVelocity in his solution would have been overkill — only a finite number of tokens are defined, so why do this?

This kindled an idea — what if we could define such tokens in the Sitecore Client? How would one go about doing that?

This post shows how I did just that, and used NVelocity via a utility class I had built for my article discussing NVelocity

I first created two templates: one that defines the Standard Values variable token — I named this Variable — and the template for a parent Master Variables folder item — this has no fields on it, so I’ve omitted its screenshot:

variable-template

I then defined some Glass.Sitecore.Mapper Models for my Variable and Master Variables templates — if you’re not familiar with Glass, or are but aren’t using it, I strongly recommend you go to http://www.glass.lu/ and check it out!

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

using Sitecore.Diagnostics;
using Sitecore.Reflection;

using Glass.Sitecore.Mapper.Configuration.Attributes;

namespace Sitecore.Sandbox.Model
{
    [SitecoreClass(TemplateId="{E14F91E4-7AF6-42EC-A9A8-E71E47598BA1}")]
    public class Variable
    {
        [SitecoreField(FieldId="{34DCAC45-E5B2-436A-8A09-89F1FB785F60}")]
        public virtual string Token { get; set; }

        [SitecoreField(FieldId = "{CD749583-B111-4E69-90F2-1772B5C96146}")]
        public virtual string Type { get; set; }

        [SitecoreField(FieldId = "{E3AB8CED-1602-4A7F-AC9B-B9031FCA2290}")]
        public virtual string PropertyName { get; set; }

        public object _TypeInstance;
        public object TypeInstance
        {
            get
            {
                bool shouldCreateNewInstance = !string.IsNullOrEmpty(Type) 
                                                && !string.IsNullOrEmpty(PropertyName) 
                                                && _TypeInstance == null;

                if (shouldCreateNewInstance)
                {
                    _TypeInstance = CreateObject(Type, PropertyName);
                }

                return _TypeInstance;
            }
        }

        private object CreateObject(string type, string propertyName)
        {
            try
            {
                return GetStaticPropertyValue(type, propertyName);
            }
            catch (Exception ex)
            {
                Log.Error(this.ToString(), ex, this);
            }

            return null;
        }

        private object GetStaticPropertyValue(string type, string propertyName)
        {
            PropertyInfo propertyInfo = GetPropertyInfo(type, propertyName);
            return GetStaticPropertyValue(propertyInfo);
        }

        private PropertyInfo GetPropertyInfo(string type, string propertyName)
        {
            return GetType(type).GetProperty(propertyName);
        }

        private object GetStaticPropertyValue(PropertyInfo propertyInfo)
        {
            Assert.ArgumentNotNull(propertyInfo, "propertyInfo");
            return propertyInfo.GetValue(null, null);
        }

        private Type GetType(string type)
        {
            return ReflectionUtil.GetTypeInfo(type);
        }
    }
}

In my Variable model class, I added some logic that employs reflection to convert the defined type into an object we can use. This logic at the moment will only work with static properties, although could be extended for instance properties, and even methods on classes.

Here is the model for the Master Variables folder:

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

using Glass.Sitecore.Mapper.Configuration.Attributes;

namespace Sitecore.Sandbox.Model
{
    [SitecoreClass(TemplateId = "{855A1D19-5475-43CB-B3E8-A4960A81FEFF}")]
    public class MasterVariables
    {
        [SitecoreChildren(IsLazy=true)]
        public virtual IEnumerable<Variable> Variables { get; set; }
    }
}

I then hooked up my Glass models in my Global.asax:

<%@Application Language='C#' Inherits="Sitecore.Web.Application" %>
<%@ Import Namespace="Glass.Sitecore.Mapper.Configuration.Attributes" %>
<script runat="server">
    protected void Application_Start(object sender, EventArgs e)
    {
        AttributeConfigurationLoader loader = new AttributeConfigurationLoader
        (
            new string[] { "Sitecore.Sandbox.Model, Sitecore.Sandbox" }
        );

        Glass.Sitecore.Mapper.Context context = new Glass.Sitecore.Mapper.Context(loader);
    }

    public void Application_End()
    {
    }

    public void Application_Error(object sender, EventArgs args)
    {
    }
</script>

Since my solution only works with static properties, I defined a wrapper class that accesses properties from Sitecore.Context for illustration:

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

namespace Sitecore.Sandbox.Data
{
    public class SitecoreContextProperties
    {
        public static string DomainName
        {
            get
            {
                return Sitecore.Context.Domain.Name;
            }
        }

        public static string Username
        {
            get
            {
                return Sitecore.Context.User.Name;
            }
        }

        public static string DatabaseName
        {
            get
            {
                return Sitecore.Context.Database.Name;
            }
        }

        public static string CultureName
        {
            get
            {
                return Sitecore.Context.Culture.Name;
            }
        }

        public static string LanguageName
        {
            get
            {
                return Sitecore.Context.Language.Name;
            }
        }
    }
}

I then defined my subclass of Sitecore.Data.MasterVariablesReplacer. I let the “out of the box” tokens be expanded by the Sitecore.Data.MasterVariablesReplacer base class, and expand those defined in the Sitecore Client by using my token replacement utility class and content acquired from the master database via my Glass models:

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

using Sitecore.Collections;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Text;

using Glass.Sitecore.Mapper;

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

namespace Sitecore.Sandbox.Data
{
    public class ContentManagedMasterVariablesReplacer : MasterVariablesReplacer
    {
        private const string MasterVariablesDatabase = "master";

        public override string Replace(string text, Item targetItem)
        {
            return CreateNewTokenator().ReplaceTokens(base.Replace(text, targetItem));
        }

        public override void ReplaceField(Item item, Field field)
        {
            base.ReplaceField(item, field);
            field.Value = CreateNewTokenator().ReplaceTokens(field.Value);
        }

        private static ITokenator CreateNewTokenator()
        {
            return Utilities.StringUtilities.Tokenator.CreateNewTokenator(CreateTokenKeyValues());
        }

        private static IEnumerable<TokenKeyValue> CreateTokenKeyValues()
        {
            IEnumerable<Variable> variables = GetVariables();
            IList<TokenKeyValue> tokenKeyValues = new List<TokenKeyValue>();

            foreach (Variable variable in variables)
            {
                AddVariable(tokenKeyValues, variable);
            }
            
            return tokenKeyValues;
        }

        private static void AddVariable(IList<TokenKeyValue> tokenKeyValues, Variable variable)
        {
            Assert.ArgumentNotNull(variable, "variable");
            bool canAddVariable = !string.IsNullOrEmpty(variable.Token) && variable.TypeInstance != null;

            if (canAddVariable)
            {
                tokenKeyValues.Add(new TokenKeyValue(variable.Token, variable.TypeInstance));
            }
        }

        private static IEnumerable<Variable> GetVariables()
        {
            MasterVariables masterVariables = GetMasterVariables();

            if (masterVariables != null)
            {
                return masterVariables.Variables;
            }

            return new List<Variable>();
        }

        private static MasterVariables GetMasterVariables()
        {
            const string masterVariablesPath = "/sitecore/content/Settings/Master Variables"; // hardcoded here for illustration -- please don't hardcode paths!
            ISitecoreService sitecoreService = GetSitecoreService();
            return sitecoreService.GetItem<MasterVariables>(masterVariablesPath);
        }

        private static ISitecoreService GetSitecoreService()
        {
            return new SitecoreService(MasterVariablesDatabase);
        }
    }
}

At first, I tried to lazy instantiate my Tokenator instance in a property, but discovered that this class is only instantiated once — that would prevent newly added tokens from ever making their way into the ContentManagedMasterVariablesReplacer instance. This is why I call CreateNewTokenator() in each place where a Tokenator instance is needed.

I then wedged in my ContentManagedMasterVariablesReplacer class using a patch config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="MasterVariablesReplacer">
        <patch:attribute name="value">Sitecore.Sandbox.Data.ContentManagedMasterVariablesReplacer,Sitecore.Sandbox</patch:attribute>
      </setting>
    </settings>
  </sitecore>
</configuration>

Now, let’s see if this works.

We first need some variables:

culture-variable

ticks-variable

Next, we’ll need an item for testing. Let’s create a template with fields that will be populated using my Sitecore.Sandbox.Data.ContentManagedMasterVariablesReplacer class:

standard-values-test-page-template

Under /sitecore/content/Home, I created a new item based on this test template.

We can see that hardcoded tokens were populated from the base Sitecore.Data.MasterVariablesReplacer class:

test-item-hardcoded-tokens-populated

The content managed tokens were populated from the Sitecore.Sandbox.Data.ContentManagedMasterVariablesReplacer class:

test-item-content-managed-tokens-populated

That’s all there is to it.

Please keep in mind there could be potential performance issues with the above, especially when there are lots of Variable items — the code always creates an instance of the Tokenator in the ContentManagedMasterVariablesReplacer instance, which is pulling content from Sitecore each time, and we’re using reflection for each Variable. It would probably be best to enhance the above by leveraging Lucene in some way to increase its performance.

Further, these items should only be created/edited by advanced users or developers familiar with ASP.NET code — how else would one be able to populate the Type and Property Name fields in a Variable item?

Despite these, just imagine the big smiley your boss will send your way when you say new Standard Values variables no longer require any code changes coupled with a deployment. I hope that smiley puts a big smile on your face! 🙂

Advertisement

Kicking It Into Overdrive With NVelocity

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!