blog post image
Andrew Lock avatar

Andrew Lock

~5 min read

Creating dynamic feature flags with feature filters

Adding feature flags to an ASP.NET Core app - Part 3

In the first post in this series, I introduced the Microsoft.FeatureManagement library, and showed how to use it to add feature flags to an ASP.NET Core app.

In the second post, I introduced the companion library Microsoft.FeatureManagement.AspNetCore, and showed the ASP.NET Core-specific features it adds such as Tag Helpers and Action Filters.

In this post, I introduce feature filters, which are a much more powerful way of working with feature flags. These let you enable a feature based on arbitrary data. For example, you could enable a feature based on headers in an incoming request, based on the current time, or based on the current user's claims.

In this post I'll show how to use the two feature filters included in the Microsoft.FeatureManagement library, PercentageFilter and TimeWindowFilter.

Microsoft.FeatureManagement was introduced in a recent .NET Community Standup and is currently in preview, so some details may change when it's properly released.

Expanding feature flags beyond simple Booleans

So far in this series I've demonstrated using feature flags that are "fixed" in configuration:

{
  "FeatureManagement": {
    "Beta": false
  }
}

With this configuration, the Beta feature flag is always false for all users (until configuration changes). While this will be useful in some cases, you may often want to enable features for only some of your users, or only some of the time.

For example, maybe you're working on a new feature, and you only want a few of your users to be able to see it, in case there are any issues with it. Alternatively, maybe you're running a promotion on an store, and you only want a banner to be enabled for a specific time period. Neither of these options are possible with the simple flags we've seen so far.

Microsoft.FeatureManagement introduces an interface IFeatureFilter which can be used to decide whether a feature is enabled or not based on any logic you require. Two simple implementations are included out of the box, TimeWindowFilter and PercentageFilter, which I'll introduce below.

Enabling a feature flag based on the current time with TimeWindowFilter

The TimeWindowFilter does as its name suggests - it enables a feature for a given time window. You provide the start and ending DateTime, and any calls to IFeatureManager.IsEnabled() for the feature will be true only between those times.

The example below continues from the previous posts, so it assumes you've already installed the NuGet package. The TimeWindowFilter (and PercentageFilter) is available in the .NET Standard Microsoft.FeatureManagement library, so you can use it in any .NET Standard application. I'm going to assume it's an ASP.NET Core app for this post.

Add the feature management services in Startup.ConfigureServices, by calling AddFeatureManagement(), which returns an IFeatureManagementBuilder. You can enable the time window filter by calling AddFeatureFilter<>() on the builder:

using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.FeatureFilters;

public class Startup 
{
    public void ConfigureServices(IServiceCollection services)
    {
        //...
        services.AddFeatureManagement()
            .AddFeatureFilter<TimeWindowFilter>();
    }
}

This adds the IFeatureFilter to your app, but you need to configure it using the configuration system. Each IFeatureFilter can have an associated "settings" object, depending on the implementation. For the TimeWindowFilter, this looks like:

internal class TimeWindowSettings
{
    public DateTimeOffset? Start { get; set; }
    public DateTimeOffset? End { get; set; }
}

So let's consider a scenario: I want to enable a custom Christmas banner which goes live on boxing day at 2am UTC, and ends three days later at 1am UTC.

We'll start by creating a feature flag for it in code called ChristmasBanner

public static class FeatureFlags
{
    public const string ChristmasBanner = "ChristmasBanner";
}

Now we'll add the configuration. As before, we nest the configuration under the FeatureManagement key and provide the name of the feature. However, instead of using a Boolean for the feature, we use EnabledFor, and specify an array of feature filters.

{
  "FeatureManagement": {
    "ChristmasBanner": {
      "EnabledFor": [
        {
          "Name": "Microsoft.TimeWindow",
          "Parameters": {
            "Start": "26 Dec 2019 02:00:00 +00:00",
            "End": "29 Dec 2019 01:00:00 +00:00"
          }
        }
      ]
    }
  }

It's important you get the configuration correct here. The general pattern is identical for all feature filters:

  • The feature name ("ChristmasBanner") should be the key of an object:
  • This object should contains a single property, EnabledFor, which is an array of objects.
  • Each of the objects in the array represents an IFeatureFilter. For each filter
    • Provide the Name of the filter ("Microsoft.TimeWindow" for the TimeWindowFilter)
    • Optionally provide a Parameters object, which is bound to the settings object of the feature filter (TimeWindowSettings in this case).
  • If any of the feature filters in the array are satisfied for a given request, the feature is enabled. It is only disabled if all IFeatureFilters indicate it should be disabled.

With this configuration, the ChristmasBanner feature flag will return false until DateTime.UtcNow falls between the provided dates:

using Microsoft.FeatureManagement;

public class IndexModel : PageModel
{
    private readonly IFeatureManager _featureManager;
    public IndexModel(IFeatureManager featureManager)
    {
        _featureManager = featureManager;
        // only returns true during provided time window
        var showBanner = _featureManager.IsEnabled(FeatureFlags.ChristmasBanner);
    }
    // ...
}

The real benefit to using IFeatureFilters is that you get dynamic behaviour, but you can still control it from configuration.

Note that TimeWindowSettings has nullable values for Start and End, to give you open-ended time windows e.g. always enable until a given date, or only enable from a given date.

Rolling features out slowly with PercentageFilter

The PercentageFilter also behaves as you might expect - it only enables a feature for x percent of requests, where x is controlled via settings. Enabling the PercentageFilter follows the same procedure as for TimeWindowFilter.

Add the PercentageFilter in ConfigureServices():

using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.FeatureFilters;

public class Startup 
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddFeatureManagement()
            .AddFeatureFilter<PercentageFilter>();
    }
}

Create a feature flag:

public static class FeatureFlags
{
    public const string FancyFonts = "FancyFonts";
}

Configure the feature in configuration:

{
  "FeatureManagement": {
    "FancyFonts": {
      "EnabledFor": [
        {
          "Name": "Microsoft.Percentage",
          "Parameters": {
            "Value": 10
          }
        }
      ]
    }
  }

The PercentageSettings object consists of a single int, which is the percentage of the time the flag should be enabled. In the example above, the flag will be enabled for 10% of calls to IFeatureManager.IsEnabled(FeatureFlags.FancyFonts).

That last sentence may sound a bit off to you. Does that mean that if you call IsEnabled() twice in the same request, the PercentageFilter could give different answers? Yes!

On top of that, subsequent requests would be subject to the same potential flipping back and forth, so users could see features popping in and out as they browse your site.

Luckily, there are solutions to both of these problems built into the library, but I'm going to leave them for a later post.

The two filters shown here are built into Microsoft.FeatureManagement, but you're free to implement your own. In the next post, I'll show how to create a custom IFeatureFilter that enables features based on the currently logged in user.

Summary

Microsoft.FeatureManagement allows you to enable feature flags based on values in configuration. By using IFeatureFilter you can get dynamic behaviour, even though your configuration may be static. The TimeWindowFilter and PercentageFilter are included in the library and provide basic dynamic feature flags. To add them to your app you must enable them by calling AddFeatureFilter<>() in Startup.ConfigureServices(), and adding the appropriate configuration.

Andrew Lock | .Net Escapades
Want an email when
there's new posts?