Being able to conditionally enable or disable features in our applications without redeploying our code is a powerful tool that we can take advantage of to quickly iterate on new features in our applications. In this article, we are going to learn about Feature Flags in ASP.NET Core, and how we can use them to turn features on or off dynamically.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

What are Feature Flags?

Feature Flags are a software development technique that allows us to essentially wrap our application features in a conditional statement, providing a toggle to enable or disable the feature. Most importantly, we can toggle features during runtime, meaning that we do not need to redeploy our application to enable a new feature or disable an old or broken feature.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Ways to Toggle a Feature Flag

In ASP.NET Core, we have different ways to define feature flags, using feature flag filters.

As mentioned, the simplest form is a boolean true/false feature filter.

The next type is a time-based filter. The feature is only enabled between the start and end times defined.

Another option is to use a percentage feature filter. With this, the percentage defined determines the probability that the feature is enabled.

Finally, within ASP.NET Core, we have the targeting filter. This filter allows us to define rules for feature enablement based on a target audience, so users or groups that access our application. Once a user receives a feature, they will continue to do so, as targeting filters are sticky.

Of course, we can always create our own custom feature filters should the built-in ones not meet our requirements.

Benefits of Feature Flags

Feature Flags are a powerful tool that allows us to change the behavior of our application at runtime without the need to redeploy. This provides many benefits.

Firstly, we can deploy new features into production environments in a disabled state, sometimes known as dark deployments. The benefit of this is that we can ensure our new feature doesn’t adversely affect the rest of the application before we enable it for all of our users.

Canary deployments are also another benefit that feature flags provide us, which determines how well a new feature works. We can slowly increase the usage of our new feature, to ensure there are no performance implications or any other bugs in the code. We can then enable the feature for all users when we are happy it is stable.

A/B testing is similar to canary deployments, but the objective of them is to determine the users’ response to a new feature. For example, we could develop a new UI, and only allow a certain group or percentage of users to use the new interface, to see if they prefer it over the previous UI.

Overall, feature flags allow us to minimize the risk of deploying new features to our application. Without disruptive changes to our users, we can enable or disable a feature in case of any performance or other issues.

Enabling Feature Flags in ASP.NET Core Application

To understand how we can use Feature Flags in our applications, let’s create an ASP.NET Core application and add some feature management.

Creating Application

We’ll start with a new Web API application, either by selecting it in Visual Studio project templates or running the dotnet new webapi -n FeatureFlags command.

The first thing we must do is add the Microsoft.FeatureManagement.AspNetCore NuGet package. The FeatureManagement library manages the feature flag lifecycle behind the scenes. This gives us the ability to change feature flags at runtime without needing to restart the application.

Adding a Feature Flag

With our application created, let’s add a feature filter to appsettings.json:

{
    "FeatureManagement": {
        "BooleanFilter": true
    }
}

We define a section called FeatureManagement, which is the default name the Microsoft.FeatureManagement.AspNetCore NuGet package will look for.

Then, we create a feature called BooleanFilter, and set its value to true, meaning this feature will be enabled by default.

Next, we need to actually check the feature flag value, so let’s create an API controller FeatureFlagController:

[Route("api/[controller]")]
[ApiController]
public class FeatureFlagController : ControllerBase
{
    private readonly IFeatureManager _featureManager;

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

    [HttpGet("BooleanFilter")]
    public async Task<IActionResult> BooleanFilter()
    {
        if (await _featureManager.IsEnabledAsync("BooleanFilter"))
        {
            return Ok("Feature enabled");
        }
        else
        {
            return BadRequest("Feature not enabled");
        }
    }
}

First, we inject the IFeatureManager interface. We create a BooleanFilter endpoint where we check if our BooleanFilter feature is enabled. If it is, we return an Ok result, otherwise, we return a BadRequest.

The final thing we need to do is register the FeatureManagment package in the Program class:

builder.Services.AddFeatureManagement();

Now we are ready to test our new feature flag. Running the application and navigating to /FeatureFlag/BooleanFilter we will see an Ok result as expected.

With our application still running, let’s change the BooleanFilter value to false, and make the same request to /FeatureFlag/BooleanFilter. This time we will receive a 400 BadRequest, letting us know the feature is not enabled.

We have seen how to able a simple Feature Flag to our application, and how we can toggle it at runtime. Next, let’s create a more advanced feature flag filter.

Enhancing Our Feature Flag

Let’s introduce a percentage-based filter. In appsettings.json, we’ll add a new filter:

{
    "FeatureManagement": {
        "BooleanFilter": false,
        "PercentageFilter": {
            "EnabledFor": [
                {
                    "Name": "Percentage",
                    "Parameters": {
                        "Value":  50
                    }
                }
            ]
        }
    }
}

This feature filter has been configured with a 50-percent probability of being enabled.

In the FeatureFlagController class, let’s create a new endpoint:

[HttpGet("PercentageFilter")]
public async Task<IActionResult> PercentageFilter()
{
    if (await _featureManager.IsEnabledAsync("PercentageFilter"))
    {
        return Ok("Feature enabled");
    }
    else
    {
        return BadRequest("Feature not enabled");
    }
}

We must also add the PercentageFilter to our service registration:

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

Let’s run our application once again and navigate to /FeatureFlag/PercentageFilter. This time, we have a 50-percent chance of receiving an Ok result. If we refresh the page a couple of times, we should receive both a 200 Ok result and a 400 BadRequest.

Custom Filter

Should the built-in feature filters not be sufficient for our use case, we have the flexibility to write custom filters by implementing the IFeatureFilter interface.

Let’s write a custom filter that only allows clients who have a specific language set to use the feature.

We’ll start by adding our desired configuration to appsettings.json:

"FeatureManagement": {
    // other filters
    "CustomFilter": {
        "EnabledFor": [
            {
                "Name": "LanguageFilter",
                "Parameters": {
                    "AllowedLanguages": [
                        "en-GB",
                        "en-US"
                    ]
                }
            }
        }
    }
}

We define an object CustomFilter which is the reference we will use when checking if our feature is enabled.

Then we define a LanguageFilter, adding an array, AllowedLanguages, to the parameters. This is where we define the languages we want to be enabled for the feature.

Next, let’s create the class for LanguageFilter:

[FilterAlias(nameof(LanguageFilter))]
public class LanguageFilter : IFeatureFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;

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

    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
    {
        var userLanguage = _httpContextAccessor.HttpContext.Request.Headers["Accept-Language"].ToString();
        var settings = context.Parameters.Get<LanguageFilterSettings>();
        return Task.FromResult(settings.AllowedLanguages.Any(a => userLanguage.Contains(a)));
    }
}

First, we inject IHttpContextAccessor. This will allow us to access the Accept-Language header from the user’s request.

We implement the IFeatureFilter interface, which gives us the EvaluateAsync() method. In this method, we check for the user’s language against the parameters defined in appsettings.json, and return true if it exists.

Finally, let’s create the LanguageFilterSettings class to store the AllowedLanguages array:

public class LanguageFilterSettings
{
    public string[] AllowedLanguages { get; set; }
}

Before we can test our custom filter, we must register the filter, along with the HttpContextAccessor:

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

builder.Services.AddHttpContextAccessor();

Finally, we create a new endpoint to test this feature flag:

[HttpGet("CustomFilter")]
public async Task<IActionResult> CustomFilter()
{
    if (await _featureManager.IsEnabledAsync("CustomFilter"))
    {
        return Ok("Feature enabled");
    }
    else
    {
        return BadRequest("Feature not enabled");
    }
}

Now when we navigate to /FeatureFlag/CustomFilter and given our language is set to English, we will receive an Ok result. Let’s verify this custom filter is working by changing the AllowedLanguages array in appsettings.json to something obscure, such as ga (Irish). When we make the same request, we will receive a BadRequest.

Time Window Filter

If we want a feature to only be enabled during a certain time window, we have the TimeWindowFilter to achieve this.

Let’s add a TimeWindowFilter to our application. First, we register the filter:

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

In appsettings.json, we define the configuration for this filter:

"FeatureManagement": {
    // other filters
    "TimeWindowFilter": {
        "EnabledFor": [
            {
                "Name": "TimeWindow",
                "Parameters": {
                    "Start": "2022-09-04T10:00:00+00:00",
                    "End": "2022-09-04T11:00:00+00:00"
                }
             }
        ]
    }
}

Here, the feature will only be active between 4th Sept 2022 at 10 AM and 4th Sept 2022 at 11 AM.

Let’s create a new endpoint to test this time window:

[HttpGet("TimeWindowFilter")]
public async Task<IActionResult> TimeWindowFilter()
{
    if (await _featureManager.IsEnabledAsync("TimeWindowFilter"))
    {
        return Ok("Feature enabled");
    }
    else
    {
        return BadRequest("Feature not enabled");
    }
}

To test, let’s navigate to /FeatureFlag/TimeWindowFilter. Obviously, depending on the time zone you are currently in, the start and end window may need adjusting to receive an Ok result.

Targeting Filter

Targeting filters allow us to enable a feature for certain users or groups.  Let’s have a look at how to define a targeting filter in ASP.NET Core. We won’t actually implement this in our application, as we don’t have any authentication methods enabled.

As with the percentage filter, we need to register the targeting filter:

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

Also, we need to add the feature flag configuration to appsettings.json:

"FeatureManagement": {
  "TargetingFilter": {
    "EnabledFor": [
      {
        "Name": "Targeting",
        "Parameters": {
          "Audience": {
            "Users": [
              "[email protected]"
            ]
          }
        }
      }
    ]      
  }

This configuration will only allow [email protected] to access whatever feature we put behind the TargetingFilter flag.

Another thing we must do to enable the targeting filter is to implement the ITargetingContextAccessor interface. The FeatureManagement library uses this custom logic to determine how to find the user’s email.

As well as targeting users, we can have more flexibility by targeting groups. This time, in appsettings.json, we define an array for groups:

"FeatureManagement": {
  "TargetingFilter": {
    "EnabledFor": [
      {
        "Name": "Targeting",
        "Parameters": {
          "Audience": {
            "Users": [],
            "Groups": [
                {
                    "Name": "Beta Testers",
                    "RolloutPercentage":  20
                }
            ]
          }
        }
      }
    ]      
  }

We define a group Beta Testers, with a RolloutPercentage set to 20, meaning 20% of the group will be enabled for the feature.

Again, we need to implement the ITargetingContextAccessor interface to allow the FeatureManagement library to determine what group a user is associated with.

Other Ways to Define Feature Flags

So far, we have checked for the status of Feature Flags by using the IFeatureManager interface and defining an if else block. This isn’t the only method available to us, so let’s look at some other ways to check our Feature Flags status.

FeatureGate Attribute

In ASP.NET Core, we have the ability to wrap endpoints or full controllers in feature toggles, using the FeatureGate attribute:

[FeatureGate("BooleanFilter")]
public async Task<IActionResult> BooleanFilter()

Here, we add the FeatureGate attribute to our BooleanFilter endpoint, and use the BooleanFilter feature flag we previously defined.

When we navigate to /FeatureFlag/BooleanFilter and the feature is disabled, we receive a 404 NotFound. This is the default status code, but we can configure it to return whatever we’d like.

Middleware

If we have a more comprehensive feature, that includes changes to our application middleware, we can conditionally add this with Feature Flags:

app.UseMiddlewareForFeature<MyMiddlewareFeature>("MyMiddlewareFeatureFlag");

With this, MyMiddlewareFeature will only be added to the pipeline if MyMiddlewareFeatureFlag is enabled.

To learn more about middleware, check out our article about ASP.NET Core Middleware.

MVC Views

When building MVC applications, we may like to conditionally show or hide UI based on the status of a feature flag filter. Fortunately, ASP.NET Core provides a simple method to do so:

<feature name="MyUIFeature">
    <p>Content displayed if 'MyUIFeature' is enabled.</p>
</feature>

This also requires the following tag helper to be added to _ViewImports.cshtml:

@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

Azure App Configuration

Throughout this article, we looked at configuring feature flags locally, using appsettings.json. This is fine for local development, but when we deploy our application, this isn’t the most ideal scenario. Instead, we can use something like Azure App Configuration to manage our feature flags independently. This is preferential as we are provided with a UI for managing our features, and means we don’t need to manually alter the appsettings.json file of our application.

Conclusion

To wrap up, in this article we’ve learned what Feature Flags are, as well as some of the benefits such as being able to greatly minimize the risk of deploying new features for our applications. Then, we covered how to add Feature Flags to an ASP.NET Core application, demonstrating the different filters available.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!