Patricio Treviño

Patricio Treviño

Husband . Father . Developer

Field value interpolation in Sitecore

In a previous blog post we talked about branch templates and how they make a content author's life a little bit easier by reducing the creation process of complex tree structures to a mere click. But, unbeknownst to most, the post also introduced us to a new concept: tokens.

So, what is a token?

Remember that special item $name underneath the branch template?

Special Item Name

Well that is a token and it serves as a placeholder for the final name of the item. This gives the content author the opportunity to choose a name that makes sense for the item instead of getting a hardcoded one.

To put it simply, tokens are Sitecore's answer to the question how can I populate a field with a predefined value without hardcoding it in the standard values of the template?. The technical geeky term for this is interpolation, but I won't bore you to death with the technicalities (but in case you are interested here is a more detailed explanation).

Out of the box Sitecore provides support for a finite number of tokens. These tokens do not require any configuration or setup, simply use one of the following in any field and you should be good to go.

Token Description
$name Expands to the name of the new item entered by the content author.
$id Expands to the id of the new item.
$parentid Expands to the id of the parent of the new item.
$parentname Expands to the name of the parent of the new item.
$date Expands to the system date in yyyyMMdd format.
$time Expands to the system time in HHmmss format.
$now Expands to the system date and time in yyyyMMdd HHmmss format.

By convention, tokens are case insensitive, so it doesn't matter if you use $ParentName or $PaRenTNaME, Sitecore will know what to do with the token.

Wait... there is no token for the information I need, what now?

OK, so there are two ways we can do this:

  1. Overriding the Sitecore.Data.MasterVariablesReplacer and patching the setting MasterVariableReplacer with the new class.
  2. Adding a new processor to the expandInitialFieldValue pipeline.

Both are valid but, in my humble opinion, the latter it's more in line with the Sitecore way of doing things, whilst the former feels a little too hacky for my taste (replacing things have never been my style, I prefer composition when possible).

Having said that, I've seen many good and valid solutions online, some use the first approach, some prefer the second... but I wasn't really convinced by any of them, mainly because those using the first approach felt hacky and the others supported only one variable per processor. So I decided to come up with my own solution with openness and extensibility in mind.

The first step is creating the supporting classes and interfaces.
This set of classes and interfaces will provide the default structure and base implementation of all variable replacers (a.k.a. interpolators).

First we start with the IVariableReplacer interface.

/// <summary>
/// Replaces a variable with a specific value.
/// </summary>
public interface IVariableReplacer
{
/// <summary>
/// Gets the supported variable.
/// </summary>
IEnumerable<string> Variables { get; }
/// <summary>
/// Replaces a value from text
/// </summary>
/// <param name="text">The text to be replaced.</param>
/// <param name="targetItem">The target item being updated.</param>
/// <returns>A string that represents the replaced text.</returns>
string Replace(string text, Item targetItem);
}
view raw IVariableReplacer.cs hosted with ❤ by GitHub

The interface defines two members:

  • a Variables property that defines the name of the variable and aliases to be replaced (e.g. $path, $contentPath, etc).
  • a Replace(string, Item) method that does the interpolation.

Next is the base class for all replacers.

/// <summary>
/// Base class for a variable replacer.
/// </summary>
public abstract class VariableReplacerBase : IVariableReplacer
{
/// <inheritdoc />
public IEnumerable<string> Variables { get; } = new List<string>();
/// <summary>
/// Class constructor.
/// </summary>
/// <param name="variables">The variable and aliases to be replaced.</param>
protected VariableReplacerBase(IEnumerable<string> variables)
{
Assert.ArgumentNotNull(variables, nameof(variables));
Assert.IsTrue(variables.Any(), "At least one variable is required");
Variables = variables.Select(variable => StringUtil.EnsurePrefix('$', variable));
}
/// <inheritdoc />
public abstract string Replace(string text, Item targetItem);
}

The code speaks for itself, the only thing worth noting is the constructor that sets the Variables property with the variables coming from the inheriting class.

The second step is creating the processor.
This is easily achieved by extending the Sitecore.Pipelines.ExpandInitialFieldValue.ExpandInitialFieldValueProcessor class and overriding the void Process(ExpandInitialFieldValueArgs) method.

/// <summary>
/// Custom variable replacer.
/// </summary>
public class ReplaceCustomVariables : ExpandInitialFieldValueProcessor
{
/// <summary>
/// Gets the variables to be replaced.
/// </summary>
public List<IVariableReplacer> Replacers { get; } = new List<IVariableReplacer>();
/// <inheritdoc />
public override void Process(ExpandInitialFieldValueArgs args)
{
Assert.ArgumentNotNull(args, nameof(args));
foreach (var replacer in Replacers)
{
if (replacer.Variables.Any(variable => args.SourceField.Value.Contains(variable)))
{
args.Result = replacer.Replace(args.Result, args.TargetItem);
}
}
}
/// <summary>
/// Adds a new variable replacer.
/// </summary>
/// <param name="node">The xml node to be parsed.</param>
public void AddVariableReplacer(XmlNode node)
{
Assert.ArgumentNotNull(node, nameof(node));
var replacer = Factory.CreateObject(node, true) as IVariableReplacer;
Assert.IsNotNull(replacer, "The replacer couldn't be created");
Replacers.Add(replacer);
}
}

This class has a property Replacers and a public method AddVariableReplacer that will be used in the next steps to create and store IVariableReplacer objects.

The third step is creating specific variable replacers.
For simplicity, I'm just adding a variable replacer that will expand the variable $path to the path of the item created by the content author.

/// <summary>
/// Replaces $path with the item path.
/// </summary>
public class PathVariableReplacer : VariableReplacerBase
{
/// <summary>
/// Class constructor.
/// </summary>
public PathVariableReplacer() : base(new string[] { "path" }) { }
/// <inheritdoc />
public override string Replace(string text, Item targetItem)
{
Assert.ArgumentNotNull(text, nameof(text));
Assert.ArgumentNotNull(targetItem, nameof(targetItem));
foreach (var variable in Variables)
{
text = text.Replace(variable, targetItem.Paths.FullPath);
}
return text;
}
}

But honestly the options are endless, anything you can access via the Item class is available to the variable replacer object... so go nuts...

The last step is tying it all together via a config patch file.
We are targeting the expandInitialFieldValue pipeline and because we want to be good citizens, we add our replacer after Sitecore's own replace variable processor, that way we can guarantee Sitecore's out of the box variables will be expanded before ours.

Add as many replacers as needed to the replacers node. This maps to the Replacers property discussed above.

<configuration xmlns:patch="https://www.sitecore.com/xmlconfig/">
<sitecore>
<pipelines>
<expandInitialFieldValue>
<processor type="MyAssembly.Namespace.Pipelines.ReplaceCustomVariables, MyAssembly.Namespace"
patch:after="processor[@type='type=Sitecore.Pipelines.ExpandInitialFieldValue.ReplaceVariables, Sitecore.Kernel']">
<replacers hint="raw:AddVariableReplacer">
<replacer type="MyAssembly.Namespace.VariableReplacers.PathVariableReplacer, MyAssembly.Namespace" />
</replacers>
</processor>
</expandInitialFieldValue>
</pipelines>
</sitecore>
</configuration>

Show me an example, maybe?

After a quick build and release, the variable will be available to use in the content editor, so go ahead and create a new template and use the $path variable in any field of the template's __Standard Values as shown below.

Token Creation

Then go to any of your pages and add a new item with the newly created template. You should be able to see the end result in the editing panel.

Token Result

And that's a wrap people... I hope this helps you and your team!

Happy Coding!