Feature Toggles in Sitecore: Integrating with SXA

In the last two posts, I’ve introduced how I have implemented Feature Toggles in Sitecore as well as how to improve performance using indexing and caching. In this final post of this series, I’ll discuss how I integrated this with SXA.

When you associate a rendering with a Togglable Feature, through the mvc.renderRendering pipeline, it will control whether or not it’s rendered on the page. But what about the SXA Toolbox?

If a rendering is disabled by a Toggleable Feature, shouldn’t it also be hidden from the toolbox? Luckily, it’s pretty simple. You just need to provide an alternate implementation of Sitecore.XA.Foundation.Editing.Service.IAvailableRenderingsOrderingService. Below is my implementation of the ToggledAvailableRenderingsOrderingService:

public class ToggledAvailableRenderingsOrderingService : AvailableRenderingsOrderingService
{
    private readonly IFeatureToggleProvider _featureToggleProvider;

    public ToggledAvailableRenderingsOrderingService(IPresentationContext presentationContext, IMultisiteContext multisiteContext, IContentRepository contentRepository, IFeatureToggleProvider featureToggleProvider)
        : base(presentationContext, multisiteContext, contentRepository)
    {
        _featureToggleProvider = featureToggleProvider ?? throw new ArgumentNullException(nameof(featureToggleProvider));
    }

    public override IList<AvailableRenderingEntry> GetOrderedRenderings(Item siteItem, IList<Item> renderings = null)
    {
        var orderedRenderings = base.GetOrderedRenderings(siteItem, renderings);

        var disabledRenderingIds = _featureToggleProvider.GetDisabledRenderings().ToList();
        if (!disabledRenderingIds.Any())
        {
            return orderedRenderings;
        }

        foreach (var id in disabledRenderingIds)
        {
            var disabledRendering = orderedRenderings.FirstOrDefault(x => string.Equals(x.RenderingItem.ID.ToString(), id, StringComparison.InvariantCultureIgnoreCase));
            if (disabledRendering != null)
            {
                orderedRenderings.Remove(disabledRendering);
            }
        }

        return orderedRenderings;
    }
}

You can patch this implementation in nice and easy, like:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <services>
      <register serviceType="Sitecore.XA.Foundation.Editing.Service.IAvailableRenderingsOrderingService, Sitecore.XA.Foundation.Editing"
                implementationType="Foundation.FeatureToggles.Editing.Service.ToggledAvailableRenderingsOrderingService, Foundation.FeatureToggles" lifetime="Singleton"
                patch:instead="*[@implementationType='Sitecore.XA.Foundation.Editing.Service.AvailableRenderingsOrderingService, Sitecore.XA.Foundation.Editing']"/>
    </services>
  </sitecore>
</configuration>

Now, when a rendering is disabled by a Toggleable Feature, the rendering won’t appear in the SXA Toolbox.

Experience Editor

What if you already have a rendering on your page that is disabled via a Toggleable Feature? If you’re in Experience Editor, would you rather it be hidden, as it is in Delivery, or maybe displayed, but with some visual indication that it’s disabled by a Toggleable Feature? I prefer the later!

To do this, we’ll add a few new classes that will add some markup to the html rendered in Experience Editor. First up, we need to provide an implementation of IMarker to toss out some HTML when called.

public class FeatureDisabledWrapperMarker : IMarker
{
    public string GetStart()
    {
        return "<div class=\"feature-disabled-wrapper\">";
    }
    public string GetEnd()
    {
        return "</div>";
    }
}

Next, we need a Wrapper object that will use this IMarker implementation:

public class ToggledFeatureWarningWrapper : Wrapper
{
    public ToggledFeatureWarningWrapper(TextWriter writer, IMarker marker) : base(writer, marker)
    {
    }
}

Now, we need to add a processor to the mvc.renderRendering pipeline that will inject this markup.

public class AddToggledFeatureWarningWrapper : RenderRenderingProcessor
{
    private readonly IPageMode _pageMode;
    private readonly IFeatureToggleProvider _featureToggleProvider;

    public AddToggledFeatureWarningWrapper(IPageMode pageMode, IFeatureToggleProvider featureToggleProvider)
    {
        _pageMode = pageMode ?? throw new ArgumentNullException(nameof(pageMode));
        _featureToggleProvider = featureToggleProvider ?? throw new ArgumentNullException(nameof(featureToggleProvider));
    }

    public override void Process(RenderRenderingArgs args)
    {
        if (args.Rendered || !_pageMode.IsExperienceEditorEditing || args.Rendering.RenderingType == "Layout")
        {
            return;
        }

        if (args.Rendering.RenderingItem != null)
        {
            var isRenderingDisabled = _featureToggleProvider.IsRenderingDisabled(args.Rendering.RenderingItem.ID.ToString());
            if (!isRenderingDisabled)
            {
                return;
            }

            args.Disposables.Add(new ToggledFeatureWarningWrapper(args.Writer, new FeatureDisabledWrapperMarker()));
        }
    }
}

Finally, we need to add one last processor to the mvc.renderRendering pipeline that will dispose of our ToggledFeatureWarningWrapper objects:

public class EndToggledFeatureWarningWrapper : RenderRenderingProcessor
{
    public override void Process(RenderRenderingArgs args)
    {
        foreach (IDisposable wrapper in args.Disposables.OfType<ToggledFeatureWarningWrapper>())
        {
            wrapper.Dispose();
        }
    }
}

Now, update your patch file that modifies the mvc.renderRendering pipeline to include these two new processors.

<pipelines>
  <mvc.renderRendering>
	<processor type="Foundation.FeatureToggles.Pipelines.RenderRendering.CheckFeatureToggle, Foundation.FeatureToggles" 
                resolve="true"
		patch:before="*[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.InitializeProfiling, Sitecore.Mvc']" />
	<processor patch:after="processor[@type='Sitecore.Mvc.ExperienceEditor.Pipelines.Response.RenderRendering.AddWrapper, Sitecore.Mvc.ExperienceEditor']" resolve="true"
		type="Foundation.FeatureToggles.Pipelines.RenderRendering.AddToggledFeatureWarningWrapper, Foundation.FeatureToggles"/>
	<processor patch:before="processor[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.AddRecordedHtmlToCache, Sitecore.Mvc']" resolve="true"
		type="Foundation.FeatureToggles.Pipelines.RenderRendering.EndToggledFeatureWarningWrapper, Foundation.FeatureToggles"/>
  </mvc.renderRendering>
</pipelines>

Last, but not least, add just a little CSS to add the “visual indication” that the rendering is disabled.

.feature-disabled-wrapper {
    background-color: #F8FF00cc;
    border: solid 2px #CBD000;
 
    .component-content {
         opacity: 30%;
     }
 }

Conclusion

Hopefully, these three posts have provided some insight on how you might implement Feature Toggles in Sitecore and integrate them with SXA.

Happy Sitecore trails, my friend!

Leave a comment