Feature Flags in .NET and How I Use Them for A/B Testing

Feature Flags in .NET and How I Use Them for A/B Testing

READ TIME - 6 MINUTES

Thank you to our sponsors who keep this newsletter free to the reader:

Progress prepared a free eBook for you - Blazor Hybrid and Web in One Solution. Dive into Blazor cross-platform apps, potential roadblocks, testing with Razor components, and more. Get the Blazor eBook here.

IcePanel is a collaborative C4 model modelling & diagramming tool that helps explain complex software systems. With an interactive map, you can align your software engineering & product teams on technical decisions across the business. Check it out here.

The ability to conditionally turn features on or off in your application without redeploying the code is a powerful tool.

It lets you quickly iterate on new features and frequently integrate your changes with the main branch.

You can use feature flags to achieve this.

Feature flags are a software development technique that allows you to wrap application features in a conditional statement. You can then toggle the feature on or off in runtime to control which features are enabled.

We have a lot to cover in this week's newsletter:

  • Feature flag fundamentals in .NET
  • Feature filters and phased rollouts
  • Trunk-based development
  • A/B testing

Let's dive in!

Feature Flags In .NET

Feature flags provide a way for .NET and ASP.NET Core applications to turn features on or off dynamically.

To get started, you need to install the Microsoft.FeatureManagement library in your project:

Install-Package Microsoft.FeatureManagement

This library will allow you to develop and expose application functionality based on features. It's useful when you have special requirements when a new feature should be enabled and under what conditions.

The next step is to register the required services with dependency injection by calling AddFeatureManagement:

builder.Services.AddFeatureManagement();

And you are ready to create your first feature flag. Feature flags are built on top of the .NET configuration system. Any .NET configuration provider can act as the backbone for exposing feature flags.

Let's create a feature flag called ClipArticleContent in our appsettings.json file:

"FeatureManagement": {
  "ClipArticleContent": false
}

By convention, feature flags have to be defined in the FeatureManagement configuration section. But you can change this by providing a different configuration section when calling AddFeatureManagement.

Microsoft recommends exposing feature flags using enums and then consuming them with the nameof operator. For example, you would write nameof(FeatureFlags.ClipArticleContent).

However, I prefer defining feature flags as constants in a static class because it simplifies the usage.

// Using enums
public enum FeatureFlags
{
    ClipArticleContent = 1
}

// Using constants
public static class FeatureFlags
{
    public const string ClipArticleContent = "ClipArticleContent";
}

To check the feature flag state, you can use the IFeatureManager service. In this example, if the ClipArticleContent feature flag is turned on, we will return only the first thirty characters of the article's content.

app.MapGet("articles/{id}", async (
    Guid id,
    IGetArticle query,
    IFeatureManager featureManager) =>
{
    var article = query.Execute(id);

    if (await featureManager.IsEnabledAsync(FeatureFlags.ClipArticleContent))
    {
        article.Content = article.Content.Substring(0, 50);
    }

    return Results.Ok(article);
});

You can also apply feature flags on a controller or endpoint level using the FeatureGate attribute:

[FeatureGate(FeatureFlags.EnableArticlesApi)]
public class ArticlesController : Controller
{
   // ...
}

This covers the fundamentals of using feature flags, and now, let's tackle more advanced topics.

Feature Filters And Phased Rollouts

The feature flags I showed you in the previous section were like a simple on-off switch. Although practical, you might want more flexibility from your feature flags.

The Microsoft.FeatureManagement package comes with a few built-in feature filters that allow you to create dynamic rules for enabling feature flags.

The available feature filters are Microsoft.Percentage, Microsoft.TimeWindow and Microsoft.Targeting.

Here's an example of defining a ShowArticlePreview feature flag that uses a percentage filter:

"FeatureFlags": {
  "ClipArticleContent": false,
  "ShowArticlePreview": {
    "EnabledFor": [
      {
        "Name": "Percentage",
        "Parameters": {
          "Value": 50
        }
      }
    ]
  }
}

This means the feature flag will be randomly turned on 50% of the time. The downside is the same user might see different behavior on subsequent requests. A more realistic scenario is to have the feature flag state be cached for the duration of the user's session.

To use the PercentageFilter, you need to enable it by calling AddFeatureFilter:

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

Another interesting feature filter is the TargetingFilter, which allows you to target specific users. Targeting is used in phased rollouts, where you want to introduce a new feature to your users gradually. You start by enabling the feature for a small percentage of users and slowly increase the rollout percentage while monitoring how the system responds.

Trunk-based Development and Feature Flags

Trunk-based development is a Git branching strategy where all developers work in short-lived branches or directly in the trunk, the main codebase. The "trunk" is the main branch of your repository. If you're using Git, it will be either the main or master branch. Trunk-based development avoids the "merge hell" problem caused by long-lived branches.

So, how do feature flags fit into trunk-based development?

The only way to ensure the trunk is always releasable is to hide incomplete features behind feature flags. You continue pushing changes to the trunk as you work on the feature while the feature flag remains turned off on the main branch. When the feature is complete, you turn on the feature flag and release it to production.

Trunk based development.

How I Used Feature Flags for A/B Testing On My Website

A/B testing (split testing) is an experiment where two or more variants of a page (or feature) are shown randomly to users. Statistical analysis is performed in the background to determine which variation performs better for a given conversion goal.

Here's an example A/B test I performed on my website:

Split test with two variants.

The hypothesis was that removing the image and focusing on the benefits would make more people want to subscribe. I measure this using the conversion rate, which is the number of people visiting the page divided by the number of people subscribing.

I'm using a platform called Posthog to run experiments, which automatically calculates the results.

You can see that the test variant has a significantly higher conversion rate, so it becomes the winner of the A/B test.

Split test with two variants experiment results.

Takeaway

The ability to dynamically turn features on or off without deploying the code is like a superpower. Feature flags give you this ability with very little work.

You can work with feature flags in .NET by installing the Microsoft.FeatureManagement library. Feature flags build on top of the .NET configuration system, and you can check the feature flag state using the IFeatureManager service.

Another use case for feature flags is A/B testing. I run weekly experiments on my website, testing changes that will improve my conversion rate. Feature flags help me decide which version of the website to show to the user. And then, I can measure results based on the user's actions.

I also made a video about feature flagging in .NET, and you can watch it here if you want to learn more.

Hope this was valuable.

Stay awesome!


Whenever you're ready, there are 3 ways I can help you:

  1. Pragmatic Clean Architecture: This comprehensive course will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture. Join 2,400+ students here.
  2. Patreon Community: Join a community of 1,050+ engineers and gain access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses. Join 1,050+ engineers here.
  3. Promote yourself to 42,000+ subscribers by sponsoring this newsletter.

Become a Better .NET Software Engineer

Join 42,000+ engineers who are improving their skills every Saturday morning.