Injecting Fragments in Optimizely XHtmlStrings

Injecting fragments in Optimizely XHtmlStrings is quite easy using a custom display template to render the property. Here is an example on how you can inject custom data based on Optimizely blocks dropped in the XHtmlString property.

I’ve been using an approach similar to this to inject ids used for anchor tag navigation also within free text.

@Html.PropertyFor(p => p.XhtmlContent, new { wrapper = "div" })

The interesting thing in the custom display template is the call to the inject fragments method.

@using EPiServer.Core;
@model XhtmlString?

@{
  var wrapper = ViewData["wrapper"] as string;
  ViewData["wrapper"] = null;
}

@if (Model != null)
{
  var withInjectedFragments = InjectFragments(Model);
  if (string.IsNullOrEmpty(wrapper))
  {
    Html.RenderXhtmlString(withInjectedFragments);
  }
  else
  {
    var tagBuilder = new TagBuilder(wrapper);
    @tagBuilder.RenderStartTag();
    Html.RenderXhtmlString(withInjectedFragment);
    @tagBuilder.RenderEndTag();
  }
}

In the InjectFragments method, we will create a new XhtmlString object containing our manipulated fragments. As we are working with blocks dropped in the XhtmlString by the editor, we can ignore all of the non ContentFragment objects. These can be added to our return object without change.

In this example, we’re using an interface to identify blocks of the type(s) we are looking for. After loading the content referenced by the ContentFragment, if the content is not of the type we want, it can also just be added without change.

public XhtmlString? InjectFragments(XhtmlString xhtmlString)
{
  var withInjectedFragments = new XhtmlString();

  foreach (var fragment in xhtmlString.Fragments)
  {
    var contentFragment = fragment as ContentFragment;
    if (contentFragment == null)
    {
      // We only care about content fragments. All others are added as is.
      withInjectedFragments.Fragments.Add(fragment);
      continue;
    }

    var content = _contentLoader.Service.Get<IContent>(contentFragment.ContentLink);
    if (content is not IIdentifyTheBlockWeAreLookingFor)
    {
      // We use an interface to identify the appropriate content.
      withInjectedFragments.Fragments.Add(fragment);
      continue;
    }

    var someData = ... // Use content to get some data from the block.
    var newFragment = new StaticFragment($@"<span>{someData}</span>");
    withInjectedFragments.Fragments.Add(newFragment);
    withInjectedFragments.Fragments.Add(fragment);
  }

  return withInjectedFragments;
}

Finally, when finding our type, we know where to inject our data.