How to implement feature flags and filters in .NET apps?

A step-by-step guide

Along with improved DevOps expertise comes a better performance of delivery teams. And when many deployments take place at the same time, new risks arise. To avoid rolling back the code and improve deployment frequency, you can use a powerful tool to accelerate your workflow – namely, feature flags.

In my previous article, I explained that we also call them ‘feature toggles.’ They enhance the work quality of development teams and make cloud applications run smoothly hence a better user experience.

If you haven’t read the post yet, I encourage you to start from there.

And now, I want to give you a brief overview of the implementation. This article will be a ready-to-use guide with a collection of practical hints to keep in mind.

Of course, it is possible to do that from scratch but usually, we do not want to reinvent the wheel.

So, is there any framework or library that we could use?

Luckily, for .NET applications, there is. Let us jump right into it.

Key points:

  • What the Microsoft.FeatureManagement library has to do with feature flags?
  • How can you implement feature flags and feature filters?
  • How to dynamically assign feature flag values using feature filters?
  • What are the steps for hiding specific endpoints using feature gates?

What is the Microsoft.FeatureManagement library?

By looking at the name, you have probably already guessed what it is. But let me explain it for clarity.

The Microsoft.FeatureManagement library is an open-source library powered by Microsoft used for feature flag management.

It is easy to integrate with ASP.NET Core as it utilizes its standard features like configuration binding.

Moreover, the library gives a lot of flexibility. Developers can use it in both simple on/off feature scenarios and more complex custom ones (e.g., when a feature toggle is based on a special header in the HTTP request body).

Let’s see that in action.

How to implement feature flags?

First, we need to install the NuGet package.

dotnet add package Microsoft.FeatureManagement

Then we register feature management classes in the DI container.

using Microsoft.FeatureManagement;

namespace Predica.FeatureFlagsExample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // ...
            services.AddFeatureManagement();
            // ...
        }
    }
}

And that’s it! We have determined minimal configuration needed to add our first feature flag.

Now, we will start with a simple on/off feature toggle. It will allow us to deploy a release flag later on.

As you may remember from my previous article, we used it in the promotion coupon example. In other words, when an online shop wants to add a new feature to reduce the price of certain items.

So, when we want to implement the release flag, we start with adding a static class listing all feature flags:

public static class FeatureFlags
{
    public const string UsePromotionCoupons = nameof(UsePromotionCoupons);
}

Then we wrap the promotion coupons logic with a feature toggle:

public class OrderPriceCalculator 
{
    private IFeatureManager _featureManager;

    public OrderPriceCalculator(
        IFeatureManager featureManager)
    {
        _featureManager = featureManager;
    }

    public async Task<OrderTotalPrice> GetTotalPrice()
    {
        var orderPrice = GetOrderPrice();

        var usePromotionCoupons = await _featureManager
            .IsEnabledAsync(FeatureFlags.UsePromotionCoupons);
        if (usePromotionCoupons)
        {
            orderPrice = GetDiscountPrice(orderPrice);
        }

        return new OrderTotalPrice(orderPrice);
    }
    // ...
}

As you have probably noticed, there appears the IFeatureManager interface that comes from the Microsoft.FeatureManagement library.

Now, look at the following line. Its function is to retrieve the feature flag value from the configuration.

var usePromotionCoupons = await _featureManager.IsEnabledAsync(
    FeatureFlags.UsePromotionCoupons);

Finally, we need to place the feature flag value somewhere. How to do that?

The easiest way would be to add it to the appsettings.json file under the FeatureManagement section, like in the code sample below:

{
  // ...
  "FeatureManagement": {
    "UsePromotionCoupons": true
  },
  // ...
}

To modify the feature toggle value locally, you will only need to change the app settings. Besides, you can use any configuration provider supported by ASP.NET Core.

For instance, if you store your application in Azure, you can set a feature toggle value via Azure Portal.

So, if that is the case, you may look forward to my next article, where I will explain how to manage feature flags in Microsoft Azure cloud-based applications.

What are feature filters?

Aside from the feature flag retrieval, the Microsoft.FeatureManagement library provides another interesting component called filters.

You can use them to calculate the value of the feature flag for a particular request.

Some features can be turned on only in a specific time window, for example:

1. On the weekends – for informing the user that their request will be dealt with in the coming business days,
2. On special occasions – for enabling exclusive offers, like product discounts during Black Week.

In total, there are two built-in filters:

  • Targeting filter – enables the feature for a chosen group of users,
  • Time window filter – enables the feature in a specific time window (e.g., next weekend only).

They may come in handy when dealing with operational and experimental toggles. If you don’t know what they are, go and check my previous article, where I explain them in detail.

How to use feature filters?

Suppose that a company wants to know how giving out promotional coupons would affect them. To avoid potential complaints or revenue loss in case of a faulty feature, they want to test it on 1% of application users before offering it to a wider audience.

In this scenario, we will use a targeting filter to create an experimental toggle for promotional coupons.

What we need to do first is register the filter in the IoC container and the helper service HttpContextTargetingContextAccessor.

That will provide information about the identity of the user accessing the evaluated feature.

using Microsoft.FeatureManagement.FeatureFilters;

namespace Predica.FeatureFlagsExample;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...
        services.AddSingleton<
            ITargetingContextAccessor,
            HttpContextTargetingContextAccessor>();
        services.AddFeatureManagement()
            .AddFeatureFilter<TargetingFilter>();
        // ...
    }
}

Now we just need to update appsettings.json, and we are good to go.

The DefaultRolloutPercentage parameter means that 1% of application users will get access to the promotion coupons feature.

{
  ...
  "FeatureManagement": {
    "UsePromotionCoupons": {
        "EnabledFor": [
            {
                "Name": "Microsoft.Targeting",
                "Parameters": {
                    "DefaultRolloutPercentage": 1
                },
            },
        ],
    },
  },
  ...
}

So, we created the feature filter, and it is ready to be tested.

However, it may happen that after consulting another stakeholder, the PO wants to have the feature enabled only for specially selected beta users’ group (who already have special claims in the application).

For that to happen, we will use ASP.NET Core user claims.

First, we will build a custom filter and apply it to the promotion coupons feature. And to store the type of the required user claim, we will need to create a settings POCO class.

namespace Predica.FeatureFlagsExample;

public class ClaimsCheckFeatureFilterSettings
{
    public string ClaimType { get; set; }
}

Now, it is time for a feature filter. We will start with implementing the IFeatureFilter interface.

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.FeatureManagement;

namespace Predica.FeatureFlagsExample;

[FilterAlias("ClaimsCheck")]
public class ClaimsCheckFeatureFilter : IFeatureFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ClaimsCheckFeatureFilter(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
    {
        var settings = context.Parameters.Get<ClaimsCheckFeatureFilterSettings>()
                       ?? new ClaimsCheckFeatureFilterSettings();
        var strValue = _httpContextAccessor.HttpContext?.User.FindFirstValue(
            settings.ClaimType);
        var parsed = bool.TryParse(strValue, out var claimValue);
        var isFeatureOn = parsed && claimValue;

        return Task.FromResult(isFeatureOn);
    }
}

Let us look at this piece of code and analyze it briefly.

As this is a regular service instantiated by a DI container, we can put there whatever we need.

For claims retrieval, we will use IHttpContextAccessor, and the method EvaluateAsync will return the Boolean value of the feature flag.

The parameter is FeatureFilterEvaluationContext which is made up of the name of the feature and additional parameters needed for the filter (for our filter, we need ClaimType).

Context parameters are known as the Microsoft.Extensions.Configuration.IConfiguration, so we can use all methods for binding the configuration to an object.

In this case, we are using the Get<ClaimsCheckFeatureFilterSettings>() method to bind it to an object of ClaimsCheckFeatureFilterSettings class.

As far as filter configuration is concerned, it is stored in the appsettings.json file just like built-in filters (or, again, at any other support configuration provider).

{
  // ...
  "FeatureManagement": {
    "UsePromotionCoupons": {
        "EnabledFor": [
            {
                "Name": "ClaimsCheck",
                "Parameters": {
                    "ClaimType": "ShowBetaFeatures",
                },
            },
        ],
    },
  },
  // ...
}

Finally, we need to register the filter. Again, this step is analogous to adding built-in filters.

using Microsoft.FeatureManagement;

namespace Predica.FeatureFlagsExample;

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

And there you go!

UsePromotionCoupons flag turns on only when the user has the ShowBetaFeatures claim.

In other words, only pre-selected beta testers can use the promotion coupons.

FEATURE FILTERS RECAP

To sum up, custom filters offer great flexibility with feature flags while keeping implementation details out of usage.

In the context of an online shop, for example, you could use those filters to apply extra features such as discounts, free shipping, or other exclusive offers for a pre-defined target group like new or returning customers.

By following this approach, you will also take advantage of customer feature flags, for example, on social media platforms.

With their help, developers can make it possible for the premium users to see who visited their profile, while free accounts cannot access that data.

Want more updates like this? Leave your email address to get the latest insights every two weeks. Subscribe

How to hide the whole endpoint?

Sometimes the feature flag will need to apply to the whole HTTP request.

Theoretically, you do not have to take any action. If the feature is disabled, then users cannot use it.

However, it may happen that a malicious user visits your site and wants to take advantage of the unfinished and hidden but not disabled feature.

And now, let’s say that on the very same website, you have an endpoint that makes it possible to redeem a promotion coupon and get a discount.

If the feature is not ready and there is no request throttling, malicious users could conduct a brute force attack and steal the available coupons in the app, causing a financial loss for the business.

Apart from that, restricting access to coupons might take some time for development teams to fix, and at the same time, application users might get angry and leave for good if the business decides to ban the coupons.

// DiscountController.cs

[HttpGet("discount")]
public IActionResult GetDiscount([FromQuery]string couponName)
{
    return CalculateCouponDiscount(couponName);
}

What can you do about that?

We will once again resort to the Microsoft.FeatureManagement library. It provides a solution called feature gates.

They are attributes placed at ASP.NET Core MVC actions.

If the feature is disabled, the request to the given action will return a 404 (Not Found) HTTP status code.

To avoid an error or security incident, you only need to add the FeatureGate attribute.

// DiscountController.cs

[FeatureGate(FeatureFlags.UsePromotionCoupons)]
[HttpGet("discount")]
public IActionResult GetDiscount([FromQuery]string couponName)
{
    return CalculateCouponDiscount(couponName);
}

It is also possible to specify multiple feature flags.

As far as RequirementType goes, you can enable all flags to grant access to the endpoint (this is the default behavior) or switch on at least one flag.

Let’s say that we have two feature flags:

  • UseSingleUsedPromotionCoupons – controls single-use coupons,
  • UseTimeLimitedPromotionCoupons – handles limited-period reusable coupons.

Both feature flags require the endpoint to apply the discount, so it needs to be accessible whenever a given flag is enabled. In a situation where there are multiple feature flags, RequirementType.Any can help.

// DiscountController.cs

[FeatureGate(
    RequirementType.Any, 
    FeatureFlags.UseSingleUsedPromotionCoupons,
    FeatureFlags.UseTimeLimitedPromotionCoupons)]
[HttpGet("discount")]
public IActionResult GetDiscount([FromQuery]string couponName)
{
    return CalculateCouponDiscount(couponName);
}

Finally, we can change the behavior when the feature gate is closed.

For instance, it can redirect the user to a 400 Bad Request instead of returning a Not Found response that, for some reason, cannot be handled properly (it may happen in some no-code solutions).

To make that change, implement IDisabledFeaturesHandler.

public class DisabledFeaturesHandler : IDisabledFeaturesHandler
{
    public Task HandleDisabledFeatures(
        IEnumerable<string> features,
        ActionExecutingContext context)
    {
        context.Result = new BadRequestResult();
        return Task.CompletedTask;
    }
}

Then, we need to register it in the DI container. Just like we did in the feature filters case.

using Microsoft.FeatureManagement;

namespace Predica.FeatureFlagsExample;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...
        services.AddFeatureManagement()
                .UseDisabledFeaturesHandler(new DisabledFeaturesHandler());
            // ...
    }
}

And here we are. From now on, feature gates will return Bad Request instead of Not Found when someone wants to access the disabled feature.

Summary

The Microsoft.FeatureManagement library is a powerful tool for managing feature toggles and filters in .NET applications.

Also, since it is integrated with ASP.NET Core, you can easily tailor it to your application needs.

I hoped you enjoyed the guide and that you are ready to implement your toggles. And if you need assistance with your cloud application, make sure to let us know.

And don’t miss out on my next post, where you will read about Azure App Configuration for managing feature flags in Microsoft Azure cloud-based applications.

Stay tuned!

Key takeaways:

  1. Microsoft.FeatureManagement library makes it possible to implement feature flags in .NET apps in an easy way.
  2. There are built-in feature filters for filtering features by target or time window.
  3. If built-in feature filters are not enough, you can easily create your own.
  4. You can use feature gates to prevent users from accessing the feature.

Further reading

Microsoft.FeatureManagement library documentation

460-million-dollar deployment mistake