Feature Flags in Asp.Net

July 3, 2023 — 7 minutes read time

Feature flagging is a technique to switch on or off specific features of an application.

Why would you like to disable a feature, you might ask? The answer could vary depending on the situation.

One reason to disable a feature is that it is not ready yet for production. This scenario usually happens when the development team uses a trunk-based branching strategy.

As the implementation of the feature progresses, developers commit the code to trunk. Yet, since it’s not ready for delivery, they hide it behind a feature flag.

This way of working has at least two benefits:

  1. they are not using a long-lived feature branch, so they avoid most of the merge conflicts
  2. although the code is already merged, the incomplete feature is not delivered yet to the end user

Once the feature is ready for delivery, product managers might want to enable it only for a subset of users. This technique is known as canary release.

The reason behind using canary releases could be to get some early feedback before releasing the feature to a wider audience. As a side note, feedback in this context could mean either feedback from end users or feedback in form of usage metrics or performance metrics.

Canary releases could be approached in multiple ways:

  1. enable the feature for a predefined percentage of users
  2. enable the feature for a subset of users chosen based on some criteria

Besides these use cases, there are also a number of other scenarios. For example, you might also want to enable a feature only withing a given period of time.

If you want to learn more about feature flags, then Martin Fowler gives a really good and comprehensive introduction into this topic here.

Feature management in ASP.NET

Now, let’s move on to the ASP.NET side of things. Microsoft provides a package that could be used to quickly and easily implement feature flags in your applications.

The main benefits of using this package insted of relying on the standard configuration packages and if statmens are the following:

Get started

To get started, add the following package to your project:

dotnet add package Microsoft.FeatureManagement.AspNetCore

To register the services provided by the package, add the following lines to the Program.cs file:


using Microsoft.FeatureManagement;

...
// By default, this will get feature flags from the "FeatureManagement"
// section of the appsettings.json configuration file.
builder.Services.AddFeatureManagement();

The simples way to configure the state of feature flags is by using the default configuration source, the appsettings.json file. Add a new section to the file, like in the example below. Make sure to use your features and their desired state. Note that this is an example of using simple feature flags, aka a feature toggle.

{
  "FeatureManagement": {
    "FeatureA": true,
    "FeatureB": false,
  }
}

As a best practice, you might also want to create a static class that defines all the features as constants. This avoids hardcoding the feature names all over the code as you can use the exposed constants instead.

public static class FeatureFlags
{
  public const string FeatureA = "FeatureA";
  public const string FeatureB = "FeatureB";
}

Use the IFeatureManager interface to inject the FeatureManager inside any class. The FeatureManager provides a few asynchronous methods to check if a given feature is enabled or not.

class MyClass {
  private readonly IFeatureManager _featureManager;

	public MyClass(IFeatureManager featureManager) {
		_featureManager = featureManager;
	}
	
	public async Task DoSomething() {
    if (await featureManager
        .IsEnabledAsync(FeatureFlags.FeatureA))
    {
        // Feature A code...
    }
  }
}

Within a controller, use the FeatureGate attribute either over the entier controller or over a method to wrap them with a feature flag. Any request to a disabled endpoint will result in a 404 error.

using Microsoft.FeatureManagement.Mvc;

// Apply feature flag to the entier controller:
[FeatureGate(FeatureFlags.FeatureB)]
public class FeatureBController : ControllerBase
{
    
}

// Apply a feature flag to a single endpoint:
[ApiController]
public class FeatureBController : ControllerBase
{
  [HttpGet]
  [FeatureGate(FeatureFlags.FeatureB)]
  public IActionResult Get()
  {
    return Ok();
  }
}

Feature filters

The package also provides a few feature filters. For simple feature toggles described in the previous section, you don’t need feature filters. However, if you want to use some of the more complex scenarios described in the introduction, then you can take advantage of the following filters:

How to use PercentageFilter

As mentioned before, the PercentageFilter could be used to implement percentage based canary releases.

Note, that in this scenario the feature’s state is not sticky to the user. This practically means that the same user will see the feature enabled only for a given percentage of the times.

For this filter, the configuration section in appsettings.json should look like the below:

{
  "FeatureManagement": {
    "FeatureA": {
      "EnabledFor": [{
        "Name": "Microsoft.Percentage",
        "Parameters": {
          "Value": 30
        }
      }]
  }
}

The Value parameter represents the percentage of time for which FeatureA will be enabled.

Additionally, in Program.cs you have to register the PercentageFilter as follows:

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

...

builder.Services.AddFeatureManagement()
            .AddFeatureFilter<PercentageFilter>();

...

How to use TimeWindowFilter

The TimeWindowFilter is useful when you need to enable a feature only for a given period of time. Use the below format in configuration to specify the time period during which the feature is on:

{
  "FeatureManagement": {
    "FeatureA": {
      "EnabledFor": [
        {
          "Name": "Microsoft.TimeWindow",
          "Parameters": {
            "Start": "1 Jul 2023 00:00:00 +00:00",
            "End": "31 Jul 2023 00:00:00 +00:00"
          }
        }
      ]
    }
  }

Of course, you also have to register the TimeWindowFilter with the service container:

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

...

builder.Services.AddFeatureManagement()
            .AddFeatureFilter<TimeWindowFilter>();

How to use TargetingFilter

Finally, when you need to use a more complex criteria to enable a feature only for a subset of users, the TargetingFilter is the right choice.

The TargetingFilter could be configured to enable the feature for a list of users or for a group of users. It is also supporting percentage based rollouts for the given audiance. This means that for example you can configure the feature flag to be enabled for only a given percentage of a group of users. The percentage could be configured with the RolloutPercentage parameter as show in the example below.

It’s also important to highlight that in this scenario, the state of the feature flag is sticky. Once it’s enabled for a given user, it will stay enabled forever for that user and vice versa.

In this example, we’ll have two groups. For simplicity, the first group represents users accessing the application from localhost. The other group is accessing it from the internet. For the localhost group we’re enabling the feature for all the users, for the other group we enable it only for half of the users.

{
  "FeatureManagement": {
    "FeatureA": {
      "EnabledFor": [{
          "name": "Microsoft.Targeting",
          "parameters": {
            "Audience": {
              "Groups": [
                {
                  "Name": "localhost",
                  "RolloutPercentage": 100
                },
                {
                  "Name": "internet",
                  "RolloutPercentage": 50
                }
              ],
              "DefaultRolloutPercentage": 0
            }
          }
        }]
    }
  }
}

The TargetingFilter expects a TargetingContext based on which it could decide who is the current user and in which group it belongs to. To generate a TargetingContenxt, you have to create a class that implements the ITargetingContextAccessor interface, as show in the example below.

using System.Net;
using Microsoft.FeatureManagement.FeatureFilters;

public class IpTargetingContextAccessor : ITargetingContextAccessor
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private const string TargetingContextLookup = "IpTargetingContextAccessor.TargetingContext";

    public IpTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    public ValueTask<TargetingContext> GetContextAsync()
    {
        var httpContext = _httpContextAccessor.HttpContext;
        var ipAddress = httpContext.Connection.RemoteIpAddress;

        List<string> groups = new List<string>();
        
        if (IPAddress.IsLoopback(ipAddress))
        {
            groups.Add("localhost");
        }
        else
        {
            groups.Add("internet");
        }
        
        TargetingContext targetingContext = new TargetingContext
        {
            Groups = groups
        };
        
        httpContext.Items[TargetingContextLookup] = targetingContext;
        
        return new ValueTask<TargetingContext>(targetingContext);
    }
}

As a last step, you have to register both the TargetingFilter and the above class with your service container.

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

...

builder.Services.AddFeatureManagement()
  .AddFeatureFilter<TargetingFilter>();

builder.Services
  .AddTransient<ITargetingContextAccessor, IpTargetingContextAccessor>();

Conclusion

As we saw, feature flagging is a powerful technique that makes it possible to switch features on or off at runtime based on various conditions. Luckily Microsoft provides a powerful yet simple library that helps with managing feature flags within your ASP.NET applications.

In this article, we’ve been looking into how simple feature toggles work and we’ve also learned how to take advantege of various pre-defined feature filters, like the PercentageFilter, the TimeWindowFilter and the TargetingFilter.

While other configuration methods also exists, for now we’ve sticked with the default and simplest configuration source, the appsettings.json file.

In this GitHub repository I’ve published some examples for each feature flagging technique described in this article. Feel free to check it out, play around with the code and use it as inspiration.