There are times when it can be really useful to have the ability to turn certain features on or off in our applications. We can certainly do this by deploying code changes, but that involves a lot of effort and risk. A better approach to being able to do this is to implement the concept of feature flags.
Feature flags are switches that enable us to turn a feature on or off. It could be as simple as a toggle switch, or as complex as an evaluation process that can turn things on given certain conditions in the application. For example, you may want a feature turned on just for certain users or a certain random percentage of users as an A/B test. Or maybe you only want the feature enabled in certain environments, like your UAT environment, because it’s “not quite ready for prime time”. Or maybe you even want to let the end user turn certain features on or off based on their personal preference.
Whatever the goal, having the ability to turn features on or off without having to deploy code can be exceptionally useful. And there are a lot of different ways to approach this in .NET applications.
The Basic Approach: App Settings
The most basic approach is to add a setting to a configuration file. In .NET, that generally means the appsettings.json file. We can add the following to the file for a basic .NET API template that includes the weather endpoint.
{
"weather": {
"enabled": true,
"results": 10
}
}
Now, in our API code, we can add the following C# class:
class WeatherSettings
{
public bool Enabled { get; set; }
public int Results { get; set; }
}
And then we can update the weatherforecast endpoint:
app.MapGet("/weatherforecast", (IOptions<WeatherSettings> weatherOptions) =>
{
var settings = weatherOptions.Value;
if (!settings.Enabled)
{
return Results.NotFound("Weather forecast feature is disabled");
}
var forecast = Enumerable.Range(1, settings.Results).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return Results.Ok(forecast);
})
.WithName("GetWeatherForecast");
Now we have a simple configuration to turn our endpoint on and off and configure the number of results to return. That works fine, but configuration in this manner potentially has one big issue with it. In older versions of .NET Core, the appsettings.json file is only read and stored when the application first launches. If we update those settings, by default it doesn’t take effect until we restart the application. Thankfully, newer ASP.NET Core (and .NET Core console apps that use the Host) will detect changes automatically and apply them. It also requires you to edit a file and push, or save, a new version of the file when we want to make changes.
Feature Management
In 2019, the Microsoft Azure team introduced the FeatureManagement library, providing for an improved method of being able to manage features in an application, especially a ASP.NET Core based app. We start by including the Microsoft.FeatureManagement.AspNetCore NuGet package to our API application. The core package is actually Microsoft.FeatureManagement, but since we want a couple of additional features specific to ASP.NET, we’ll add the other package, which includes the base one as a dependency. Once that’s in place, we’ll register the service with our API application.
builder.Services.AddFeatureManagement(builder.Configuration.GetSection("flags"));
Then, we’ll update our appsettings.json file to be a little more friendly to a FeatureManagement-based setup. We’ll re-arrange the configuration as follows:
"flags": {
"forecast": true
},
"weather": {
"results": 10
}
Once we’ve registered the service and updated our config, we’ll update our weather endpoint as follows by injecting the service.
app.MapGet("/weatherforecast", async (IFeatureManager featureManager, IOptions<WeatherSettings> weatherOptions) =>
{
var settings = weatherOptions.Value;
if (!await featureManager.IsEnabledAsync("forecast"))
{
return Results.NotFound("Weather forecast feature is disabled");
}
var forecast = Enumerable.Range(1, settings.Results).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return Results.Ok(forecast);
})
.WithName("GetWeatherForecast");
Now, you’re probably thinking: “Wait a minute. We still have our WeatherSettings configuration class for the number of results.” And you’re right. We still need that class to set up the number of results to return. FeatureManagement doesn’t do all the configuration pieces. And that’s better. The library is fully focused on managing the availability of the features. It’s proper separation of concerns for our development workflow. What the FeatureManagement library does give us, however, for our API endpoints are some handy filtering features.
The ASP.NET Core version of the library provides us with several additional features that are specific to working with ASP.NET Core applications.
- Action filters to show/hide actions based on a feature enabled state
- A tag helper to show/hide sections of the UI based on a feature enabled state
- Extension methods to register routes, filters, or middleware based on a feature enabled state
Let’s take a look at how the action filters work.
Filtering
In our sample above, we forced the endpoint to return a 404 NotFound response if the feature was disabled. A better way of handling that is by implementing an endpoint filter. First, let’s create our filter class.
public class FeatureFlagFilter:IEndpointFilter
{
private readonly IFeatureManager _featureManager;
private readonly string _featureName;
public FeatureFlagFilter(IFeatureManager featureManager, string featureName)
{
_featureManager = featureManager;
_featureName = featureName;
}
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
if (await _featureManager.IsEnabledAsync(_featureName))
{
// Feature is enabled, continue to the route handler
return await next(context);
}
else
{
// Feature is disabled, short-circuit the pipeline and return a 404 Not Found
return Results.NotFound();
}
}
}
For our example API, using MinimalApi, we would update our weather endpoint as follows:
app.MapGet("/weatherforecast", async (IFeatureManager featureManager, IOptions<WeatherSettings> weatherOptions) =>
{
...
})
.AddEndpointFilter(new FeatureFlagFilter(app.Services.GetRequiredService<IFeatureManager>(), "weather"))
.WithName("GetWeatherForecast");
Note: If you’re using traditional MVC endpoints, there is a built-in filter called “FeatureGate”, and you don’t need to create your own filter.
[FeatureGate(FeatureFlags.SomeFeature)]
public class SomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
Advanced Filters
Beyond the basic true/false filtering of endpoints, FeatureManagement provides us with other, more advanced filters to work with: TimeWindowFilter, PercentageFilter, and TargetingFilter. First, we’ll need to update our Program.cs to enable these three features:
builder.Services.AddFeatureManagement(builder.Configuration.GetSection("flags"))
.AddFeatureFilter<TimeWindowFilter>()
.AddFeatureFilter<PercentageFilter>()
.AddFeatureFilter<TargetingFilter>();
The TimeWindowFilter enables us to open up a particular feature during a certain time period. For instance, let’s say we’re hosting a conference and ticket sales will only be enabled from March 14th at 1:59PM until April 15th at 11:59:59PM (assuming US Eastern timezone). We can set up our configuration as follows:
"flags": {
"ticketSales": {
"EnabledFor": [
{
"Name": "Microsoft.TimeWindow",
"Parameters": {
"Start": "14 Mar 2026 13:59:00 -05:00",
"End": "15 Apr 2026 11:59:59 -05:00"
}
}
]
}
},
As you can see, the EnabledFor is an array value, so we can set up multiple windows if need be. Aside from that, the implementation in the code is the same as the true/false feature flags. And it does accept the datetime value in a number of different formats
The PercentageFilter is the same way. We only need to specify the configuration with an integer value:
"randomUsers": {
"EnabledFor": [
{
"Name": "Microsoft.Percentage",
"Parameters": {
"Value": 50
}
}
]
}
If we had a filter like this, half of the customers would see a particular feature enabled. Not so useful for endpoints, perhaps, but quite useful for random testing of UI/UX features. The downside with this one is that it doesn’t track the user in any way and keep the state, so a user could randomly see a particular feature popping in and out of existence with each visit to a page that makes use of that filter. Still, there is some value there.
The TargetingFilter enables us to target a specific user or group of users for a feature. For our configuration, we’ll add something like the following:
"betaTesters": {
"EnabledFor": [
{
"Name": "Microsoft.Targeting",
"Parameters": {
"Audience": {
"Users": [ "user1", "user2" ],
"Groups": [
{
"Name": "betaTestersGroup",
"RolloutPercentage": 100
}
],
"DefaultRolloutPercentage": 0
}
}
}
]
}
Unlike the other two filter types, with TargetingFilter you will need to write a filter class implementing the ITargetingContextAccessor interface to use it. The reason for that is because your authentication/authorization implementation will vary from application to application and the filter will need to implement a process for extracting the username and group information in the manner specific to your application’s security context. Something like this:
public class MyTargetingFilter : ITargetingContextAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyTargetingFilter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public ValueTask<TargetingContext> GetContextAsync()
{
var context = _httpContextAccessor.HttpContext;
//Code to get username and list of groups
...
//
return new ValueTask<TargetingContext>(new TargetingContext
{
UserId = userId,
Groups = groups
});
}
}
Then you can register the filter to make use of it in the application.
builder.Services.AddSingleton<ITargetingContextAccessor, MyTargetingFilter>();
Custom Feature Filters
You also have the ability to write a custom filter of your own to implement whatever logic you can imagine. Maybe you need to implement or block certain features based on where the user is connecting from, or maybe you want to display a special welcome message to users with the first name of “Fred”. Maybe you have a secret, hidden spellbook that’s only available to users with the “wizard” role. Whatever. You just need to create a class that implements the IContextualFeatureFilter interface, register it, and you’re good to go.
Alternatives
The Microsoft.FeatureManagement library is far from the only .NET feature management library out there. It’s just the official one. There are a bunch of other libraries available with a variety of different…. features. Here are just a few of the more common ones available.
Azure Feature Management
If you deploy apps to Azure, you can write your code to take advantage of the Azure Feature Management platform. It’s pretty much the same feature set as the FeatureManagement library, but it provides you with an interface in the Azure portal to turn features on and off and configure them there instead of with appsettings configuration.
LaunchDarkly
LaunchDarkly is a paid service, and one of the more feature-rich libraries out there, and it’s been around for quite a while. I say “library”, but really it’s a full release pipeline and observability platform that includes a feature flag toolset that’s one of the deepest and most complete available, with a dizzying array of capabilities for controlling who can see and access what at any given time, all from an in-depth UI so you never have to mess with your appsettings.json files. On the downside, it’s a paid service that gets more expensive the more users you have.
Unleash
Another paid service is Unleash. It provides a full suite of feature management tools, pretty much any type of feature toggle control you can imagine. The biggest difference with Unleash is that it is 100% open-source. You can either host it and handle it all yourself, or you can opt for their paid, hosted service, with full support.
Esquio
Esquio is a free library available for feature toggles and A/B testing. It works with pretty much any .NET Core project and provides a UI to enable you to turn features on and off on the fly. It’s well built and stable. At least, it was for .NET Core up through v3. It hasn’t had any updates in several years, so it’s hard to say whether it will have any issues with more recent versions of .NET Core.
Moggles
If you worked in .NET Framework at all, you probably heard of Moggles. It has long been a go-to library for feature toggles in .NET Framework, but it also works with .NET Core. It provides a full suite of feature flag capabilities, as well as centralized monitoring for tracking features across multiple environments and applications. It focuses on “giving control to the NON-developers”, letting your business peeps turn features on and off to their heart’s content.
FeatBit
FeatBit is an open-source .NET feature flags management platform. It is still fairly actively maintained and provides a wide assortment of features for feature toggles, including sampling and “Production Testing” with real-time updates so you don’t need to mess with configuration files or redeployment of code. Like Moggles, it provides you with a centralized platform to monitor and update across multiple environments and applications.
Conclusion
Whatever route you take to feature management, the ability to decouple feature release from code deployment, turning features on and off at will, is a critical part of any application infrastructure and should always be a core part of the architecture of any enterprise level application. By providing you with the ability to make instant rollbacks, targeted deployments, subset A/B testing, and gradual implementations, you are able to vastly reduce the risk to your application infrastructure and your company’s reputation and success.