Feature Toggles in Sitecore: An Introduction

“Feature Toggles” (aka Feature Flags) are very useful tools to have in your software development toolbelt when writing code in a large application, especially when either large, or multiple, teams are working within a single application or a suite of applications. Wikipedia defines “Feature Toggles” as:

“A condition within the code enables or disables a feature during runtime.”

Wikipedia

There are many companies that specialize in feature toggles, specifically – LaunchDarkly, Optimizely, just to name a couple. These are vendor-based solutions that help you manage all of your features, roll them out slowly, integrate into your custom applications, etc… However, they are not free, so if you don’t have extra budget for these types of advanced solutions, you may think you’re out of luck. But that’s not true!

Feature Toggling is not a difficult concept to implement. It’s just a condition. If “feature is enabled”, do this. If not, do something else. In this article, I’ll explain how I implemented Feature Toggles within Sitecore. And in future articles (if I get around to them), I’ll explain how I evolved this implementation to be more performant and more useful overall.

The “Toggleable Feature”

We’ll start by creating a couple of new templates in Sitecore:

  • Toggleable Feature
  • Toggleable Feature Folder

As I will create this as a “Foundation Module”, these templates will go under /templates/Foundation/Feature Toggles. The Toggleable Feature template is pretty simple and has only two fields.

The Toggleable Feature Folder has no fields, but only has __Standard Values, so that we can easily set Insert Options to allow Toggleable Feature items to be created within this item.

Creating your First Feature

I typically manage features under /settings, so next, create a folder called Feature Toggles under /settings/Foundation using the Toggleable Feature Folder template. Then, using the Toggleable Feature template, create an item called My Feature.

Now, take note of the Item ID of the My Feature item – we’ll need that in just a little bit.

Determining Feature State

The first thing we want to do is write a repository class that will get a list of features back from Sitecore, with their settings. I’ll start with the inerface.

public interface IFeatureToggleRepository
{
    IEnumerable<Item> SearchFeatures();
}

To implement this feature is pretty simple. In this solution, we’re using SXA, so we get the benefit of some nice abstraction provided by the SXA team.

public class SitecoreFeatureToggleRepository : IFeatureToggleRepository
{
    private readonly IContentRepository _contentRepository;

    public SitecoreFeatureToggleRepository(IContentRepository contentRepository)
    {
        _contentRepository = contentRepository ?? throw new ArgumentNullException(nameof(contentRepository));
    }

    public IEnumerable<Item> SearchFeatures()
    {
        var featureToggleRoot = _contentRepository.GetItem("/sitecore/system/settings/Foundation/Feature Toggles");
        return featureToggleRoot?.Children.ToList() ?? Enumerable.Empty<Item>();
    }
}

This is a nice class that gets the raw data out of Sitecore, but I would like to encapsulate the logic of a feature toggle within a different class. Below is an interface and implementation of a “Feature Toggle Provider” that will provide that logic.

public interface IFeatureToggleProvider
{
    bool IsFeatureDisabled(string feature);
}

public class FeatureToggleProvider : IFeatureToggleProvider
{
    private readonly IFeatureToggleRepository _featureToggleRepository;

    public FeatureToggleProvider(IFeatureToggleRepository featureToggleRepository)
    {
        _featureToggleRepository = featureToggleRepository ?? throw new ArgumentNullException(nameof(featureToggleRepository));
    }

    public bool IsFeatureDisabled(string feature)
    {
        var features = _featureToggleRepository.SearchFeatures();
        if (!features.Any())
        {
            return false;
        }

        var toggleableFeature = features.FirstOrDefault(x => x.ID == ID.Parse(feature));
        if (toggleableFeature == null)
        {
            return false;
        }

        var isDisabled = toggleableFeature["Enabled"] == "0" || toggleableFeature["Enabled"] == string.Empty;
        return isDisabled;
    }
}

Next, register both of these implementations with the container.

public class RegisterServices : IServicesConfigurator
{
    public void Configure(IServiceCollection serviceCollection)
    {
        serviceCollection.AddSingleton<IFeatureToggleRepository, SitecoreFeatureToggleRepository>();
        serviceCollection.AddSingleton<IFeatureToggleProvider, FeatureToggleProvider>();
    }
}

You’ll notice in the provider above, my method is IsFeatureDisabled instead of IsFeatureEnabled. For my purposes (and this will become a little clearer when we toggle renderings a little later), I make the assumption that everything is enabled, or true, by default. So, making this assumption, I want to determine if the “inverse of default”. Meaning, I want to know if something is disabled, not enabled. Because I’m assuming that it’s enabled by default.

Using the IFeatureToggleProvider

Even with this very basic implementation, you can get started right now! There are a couple of ways you could do it.

The first way would be just a simple if/else block.

public static class Features
{
    // This is the Item ID of the `My Feature` item created in `/sitecore/system/settings/Foundation/Feature Toggles`
    public static string MyFeature = "{076F3031-6743-4956-A1E6-BCB521C539ED}";
}

public class MyFeature
{
    private readonly IFeatureToggleProvider _featureToggleProvider;

    public MyFeature(IFeatureToggleProvider featureToggleProvider)
    {
        _featureToggleProvider = featureToggleProvider ?? throw new ArgumentNullException(nameof(featureToggleProvider));
    }

    public void DoSomething()
    {
        if (_featureToggleProvider.IsFeatureDisabled(Features.MyFeature))
        {
            // do something if the feature is diabled
        }
        else
        {
            // do something else
        }
    }
}

There is a problem with this approach, however. Once the feature is considered “fully implemented” and there is no longer a need to toggle it on or off, the feature itself should be removed. So, when it comes time to clean up the code, this would be much trickier.

A cleaner, more preferred method would be to create separate implementations and let the container do the work. Let’s start by refactoring our MyFeature class.

public interface IMyFeature
{
    void DoSomething();
}

public class MyFeatureEnabled : IMyFeature
{
    public void DoSomething()
    {
        // Do something because the feature is enabled
    }
}

public class MyFeatureDisabled : IMyFeature
{
    public void DoSomething()
    {
        // Do something because the feature is disdabled
    }
}

Now, let’s abuse the container make the container do the hard stuff!

public class RegisterServices : IServicesConfigurator
{
    public void Configure(IServiceCollection serviceCollection)
    {
        serviceCollection.AddTransient<IMyFeature>(x =>
        {
            var provider = x.GetService<IFeatureToggleProvider>();
            if (provider.IsFeatureDisabled(Features.MyFeature))
            {
                return x.GetService<MyFeatureDisabled>();
            }
            else
            {
                return x.GetService<MyFeatureEnabled>();
            }
        });
    }
}

Now, once it’s time to remove the feature completely, it will be a little more straight forward to accomplish.

Toggling Renderings

If you recall, when we created the Toggleable Feature item, there was a field for Components. This field allows us to associate certain renderings with a single feature. That way, if a feature is disabled and there are already renderings on a page that are associated with this feature, we can hide those renderings.

Tapping into the mvc.renderRendering Pipeline

Now that we have our IFeatureToggleProvider already created, most of the hard work is done. We’ll just add one method to the provider, then add a processor to the mvc.renderRendering pipeline.

Let’s start by making things just a bit easier and create a POCO called ToggleableFeature.

public class ToggleableFeature
{
    public string FeatureId { get; set; }
    public List<string> RenderingIds { get; set; }
    public bool IsEnabled { get; set; }
}

Now, we’ll refactor IFeatureToggleProvider and FeatureToggleProvider as show below.

public interface IFeatureToggleProvider
{
    bool IsFeatureDisabled(string feature);
    bool IsRenderingDisabled(string rendering);
}
public class FeatureToggleProvider : IFeatureToggleProvider
{
    private readonly IFeatureToggleRepository _featureToggleRepository;

    public FeatureToggleProvider(IMyFeatureToggleRepository featureToggleRepository)
    {
        _featureToggleRepository = featureToggleRepository ?? throw new ArgumentNullException(nameof(featureToggleRepository));
    }

    public bool IsFeatureDisabled(string feature)
    {
        var features = GetFeatures();
        var isDisabled = !features?.FirstOrDefault(x => string.Equals(x.FeatureId, feature, StringComparison.InvariantCultureIgnoreCase))?.IsEnabled ?? false;
        return isDisabled;
    }

    public bool IsRenderingDisabled(string rendering)
    {
        var features = GetFeatures();
        var isDisabled = !features?.FirstOrDefault(x => x.RenderingIds != null && x.RenderingIds.Select(s => s.ToLower())
                            .Contains(rendering?.ToLower()))?.IsEnabled ?? false;

        return isDisabled;
    }

    private IEnumerable<ToggleableFeature> GetFeatures()
    {
        var features = new List<ToggleableFeature>();

        var searchResults = _featureToggleRepository.SearchFeatures();

        if (searchResults.Any())
        {
            features.AddRange(searchResults.Select(item => new ToggleableFeature
            {
                FeatureId = item.Uri.ItemID.ToString(),
                IsEnabled = item["Enabled"] == "1",
                RenderingIds = ((MultilistField)item.Fields["Components"]).TargetIDs?.Select(x => ID.Parse(x).ToString()).ToList() ?? new List<string>()
            }));
        }

        return features;
    }
}

Now that we have the logic to check the state of a rendering, let’s create a new processor to stick into the mvc.renderRendering pipeline.

public class CheckFeatureToggle : RenderRenderingProcessor
{
    private readonly IFeatureToggleProvider _featureToggleProvider;
    private readonly IPageMode _pageMode;
    private readonly IContext _context;

    public CheckFeatureToggle(IFeatureToggleProvider featureToggleProvider, IPageMode pageMode, IContext context)
    {
        _featureToggleProvider = featureToggleProvider ?? throw new ArgumentNullException(nameof(featureToggleProvider));
        _pageMode = pageMode ?? throw new ArgumentNullException(nameof(pageMode));
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public override void Process(RenderRenderingArgs args)
    {
        if (_context.GetSiteName() == "shell" || _pageMode.IsExperienceEditor)
        {
            return;
        }

        if (_featureToggleProvider.IsRenderingDisabled(args.Rendering.RenderingItem.ID.ToString()))
        {
            args.AbortPipeline();
        }
    }
}

Now add this configuration to a patch file.

<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']" />
  </mvc.renderRendering>
</pipelines>

This will insert our processor at the beginning of the pipeline. If a feature is disabled that is associated with our rendering, we just bail on the pipeline altogether. If not, we just let it continue as normal.

Conclusion

The code above will provide a nice entry point into Sitecore-based feature toggling. In upcoming posts, I’ll talk about how to improve the performance of this implementation for large sites and even how to optimize this for SXA.

Happy Sitecore trails, my friend!

Leave a comment