Releasing stuff is dangerous: you might break things, you might annoy your users or you could screw up in any number of entertaining ways.
Feature flags are a great way to get functionality into production without quite so much risk. You can release your new feature to only a small subset of your users and then roll it out once you’re happy that things aren’t on fire.
The way you define flags will depend on the requirements for roll out - sometimes a configuration setting is sufficient; sometimes you’ll need per-user settings or something more complex. That’s outside the scope of this article though - I’ll leave that part up to you.
Once you have your flags defined you want to start modifying content based on those flags. You coulds do that with a bunch of if
statements but when we’re talking about Razor it can get messy fast.
Instead, wouldn’t it be nice to wrap your new stuff in a special tag?
<feature flag="MyCoolNewThing">
<!-- cool content here -->
</feature>
Or if you want to display something only for users without the new feature?
<feature flag="MyCoolNewThing" disabled>
Click here to enable my new cool thing!
<button>Enable Now!</button>
</feature>
Tag Helpers make this easy!
The TagHelper Class
Tag helpers allow you to write server-side code that manipulates the DOM during the render of a Razor view. They can accept dependency-injected services (in the scope of the current request) and can access attributes and child content of their Razor element.
They are implemented by extending the TagHelper
class and are used in Razor views by converting the class name to a kebab-cased equivalent. e.g. MyGreatNewTagHelper
would be available via the my-great-new
tag.
The class then modifies the generated DOM by overriding either the Process
or ProcessAsync
methods. These methods are passed context objects to allow both interrogation of the current content and modification of the output.
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
//...
}
Hiding Child Content
In our case, the “modification” we want is really simple: we want to optionally hide all child content. This is directly supported via the TagHelperOutput.SuppressContent()
method so we can hide content with a 1 line method:
public override async Task ProcessAsync(
TagHelperContext context,
TagHelperOutput output)
{
output.SuppressContent();
}
note that we don’t need to be using the
ProcessAsync
variant as we have no async code yet, but we’ll be adding some shortly
Optionally Hiding Child Content
We only want to hide the child content if a feature flag is disabled, so we need to know the state of the flag. There are a lot of ways that feature flag settings could be implemented (configuration settings, per-user flags, etc.) so we are going to abstract all of that away behind an interface:
public interface IFeatureFlagProvider {
Task<bool> IsEnabled(FeatureFlag featureFlag);
}
public enunm FeatureFlag {
Unknown,
MyCoolNewThing,
AnotherAwesomeFeature,
Etc
}
The IFeatureFlagProvider
accepts an enum value identifying a feature and asynchronously returns a boolean indicating whether or not the feature is enabled. Any complexity around how you determine the availability of the feature can happily hide behind this facade.
Note: I’m using an enum to define my features but strings are a valid alternative. I prefer enums because you can find all references easily and if you’re adding new features then you’re going to be changing code anyway!
As I said above, tag helpers can accept injected dependencies so as soon as we have registered an implementation of IFeatureFlagProvider
we can use it in our helper. We’re also going to add a Flag
property which will be set on each instance of the tag.
public class FeatureTagHelper : TagHelper
{
private readonly IFeatureFlagProvider _featureFlagProvider;
public FeatureTagHelper(IFeatureFlagProvider featureFlagProvider)
{
_featureFlagProvider = featureFlagProvider;
}
public FeatureFlag Flag { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var isFeatureEnabled = await _featureFlagProvider.IsEnabled(this.Flag);
if (!isFeatureEnabled)
output.SuppressContent();
}
}
Support Disabled State
We may want to show content only when a feature is not enabled (e.g. an “enable now” message) so we want to support the inverse. One option would be to create a second tag helper but I felt like a disabled
flag made more sense when in Razor:
<feature flag="FeatureName" disabled>...</feature>
To achieve this we can use the supplied TagHelperContext
to look for a disabled attribute and then combine this with our isFeatureEnabled
condition from earlier:
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var isFeatureEnabled = await _featureFlagProvider.IsEnabled(this.Flag);
var isTagDisabled = context.AllAttributes.Where(a => a.Name?.ToLowerInvariant() == "disabled").Any();
var showContent = isFeatureEnabled != isTagDisabled;
if (!showContent)
output.SuppressOutput();
}
}
Using the TagHelper
Now that we’ve created the tag helper we can start using it in our Razor views.
Before it becomes available we will need to add it to the project in the Views/_ViewImports.cshtml
file that is created as part of the ASP.NET Core templates.
You can either add all tag helpers in your project or add each one individually.
<!-- add all in assembly MyProject -->
@addTagHelper *, MyProject
<!-- or add individually -->
@addTagHelper MyProject.TagHelpers.FeatureTagHelper, MyProject
Once that’s done you can use the new tags in any of your Razor pages or views as below:
<feature flag="MyCoolNewThing">
<!--
content will only be displayed if FeatureFlag.MyCoolNewThing is enabled
for the current user
-->
<h1>Cool New Thing</h1>
</feature>
<feature flag="MyCoolNewThing" disabled>
<!--
content will only be displayed if FeatureFlag.MyCoolNewThing is disabled
for the current user
-->
Click here to enable my new cool thing!
<button>Enable Now!</button>
</feature>