Using feature flags in an ASP.NET Core app
The .NET Core Feature Management libraries provide idiomatic support for implementing feature flags in a .NET or ASP.NET Core application. These libraries allow you to declaratively add feature flags to your code so that you don’t have to manually write code to enable or disable features with if
statements.
The Feature Management libraries also manage feature flag lifecycles behind the scenes. For example, the libraries refresh and cache flag states, or guarantee a flag state to be immutable during a request call. In addition, the ASP.NET Core library offers out-of-the-box integrations, including MVC controller actions, views, routes, and middleware.
For the ASP.NET Core feature management API reference documentation, see Microsoft.FeatureManagement Namespace.
In this post, you will learn how to:
- Add feature flags in key parts of your application to control feature availability.
- Integrate with App Configuration when you’re using it to manage feature flags.
Set up feature management
To access the .NET Core feature manager, your app must have references to the Microsoft.FeatureManagement.AspNetCore
NuGet package.
The .NET Core feature manager is configured from the framework’s native configuration system. As a result, you can define your application’s feature flag settings by using any configuration source that .NET Core supports, including the local appsettings.json file or environment variables.
By default, the feature manager retrieves feature flag configuration from the "FeatureManagement"
section of the .NET Core configuration data. To use the default configuration location, call the AddFeatureManagement method of the IServiceCollection passed into the ConfigureServices method of the Startup class.
using Microsoft.FeatureManagement;
public class Startup
{ public void ConfigureServices(IServiceCollection services)
{ ... services.AddFeatureManagement();
}
}
You can specify that feature management configuration should be retrieved from a different configuration section by calling Configuration.GetSection and passing in the name of the desired section. The following example tells the feature manager to read from a different section called "MyFeatureFlags"
instead:
using Microsoft.FeatureManagement;
public class Startup
{ public void ConfigureServices(IServiceCollection services)
{ ... services.AddFeatureManagement(Configuration.GetSection("MyFeatureFlags")); }
}
If you use filters in your feature flags, you must include the Microsoft.FeatureManagement.FeatureFilters namespace and add a call to AddFeatureFilters specifying the type name of the filter you want to use as the generic type of the method. For more information on using feature filters to dynamically enable and disable functionality, see Enable staged rollout of features for targeted audiences.
The following example shows how to use a built-in feature filter called PercentageFilter
:
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.FeatureFilters;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
... services.AddFeatureManagement() .AddFeatureFilter<PercentageFilter>(); }
}
Rather than hard coding your feature flags into your application, we recommend that you keep feature flags outside the application and manage them separately. Doing so allows you to modify flag states at any time and have those changes take effect in the application right away. The Azure App Configuration service provides a dedicated portal UI for managing all of your feature flags. The Azure App Configuration service also delivers the feature flags to your application directly through its .NET Core client libraries.
The easiest way to connect your ASP.NET Core application to App Configuration is through the configuration provider included in the Microsoft.Azure.AppConfiguration.AspNetCore
NuGet package. After including a reference to the package, follow these steps to use this NuGet package.
- Open Program.cs file and add the following code.
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => webBuilder.ConfigureAppConfiguration(config =>
{
var settings = config.Build(); config.AddAzureAppConfiguration(options => options.Connect(settings["ConnectionStrings:AppConfig"]).UseFeatureFlags());
}).UseStartup<Startup>());
2. Open Startup.cs and update the Configure
and ConfigureServices
method to add the built-in middleware called UseAzureAppConfiguration
. This middleware allows the feature flag values to be refreshed at a recurring interval while the ASP.NET Core web app continues to receive requests.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAzureAppConfiguration();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAzureAppConfiguration();
}
In a typical scenario, you will update your feature flag values periodically as you deploy and enable and different features of your application. By default, the feature flag values are cached for a period of 30 seconds, so a refresh operation triggered when the middleware receives a request would not update the value until the cached value expires. The following code shows how to change the cache expiration time or polling interval to 5 minutes by setting the CacheExpirationInterval in the call to UseFeatureFlags.
config.AddAzureAppConfiguration(options => options.Connect(settings["ConnectionStrings:AppConfig"]).UseFeatureFlags(featureFlagOptions =>
{
featureFlagOptions.CacheExpirationInterval = TimeSpan.FromMinutes(5);
}));
});
Feature flag declaration
Each feature flag declaration has two parts: a name, and a list of one or more filters that are used to evaluate if a feature’s state is on (that is, when its value is True
). A filter defines a criterion for when a feature should be turned on.
When a feature flag has multiple filters, the filter list is traversed in order until one of the filters determines the feature should be enabled. At that point, the feature flag is on, and any remaining filter results are skipped. If no filter indicates the feature should be enabled, the feature flag is off.
The feature manager supports appsettings.json as a configuration source for feature flags. The following example shows how to set up feature flags in a JSON file:
{"FeatureManagement": { "FeatureA": true, // Feature flag set to on "FeatureB": false, // Feature flag set to off "FeatureC": { "EnabledFor": [ { "Name": "Percentage", "Parameters": { "Value": 50 } } ] } } }
By convention, the FeatureManagement
section of this JSON document is used for feature flag settings. The prior example shows three feature flags with their filters defined in the EnabledFor
property:
- FeatureA is on
- FeatrueB is off
- FeatureC specifies a filter named
Percentage
with aParameters
property.Percentage
is a configurable filter. In this example,Percentage
specifies a 50-percent probability for theFeatureC
flag to be on. For a how-to guide on using feature filters, see Use feature filters to enable conditional feature flags.
Use dependency injection to access IFeatureManager
For some operations, such as manually checking feature flag values, you need to get an instance of IFeatureManager. In ASP.NET Core MVC, you can access the feature manager IFeatureManager
through dependency injection. In the following example, an argument of type IFeatureManager
is added to the signature of the constructor for a controller. The runtime automatically resolves the reference and provides an of the interface when calling the constructor. If you’re using an application template in which the controller already has one or more dependency injection arguments in the constructor, such as ILogger
, you can just add IFeatureManager
as an additional argument:
using Microsoft.FeatureManagement;
public class HomeController : Controller
{
private readonly IFeatureManager _featureManager; public HomeController(ILogger<HomeController> logger, IFeatureManager featureManager)
{
_featureManager = featureManager;
}
}
Feature flag references
Define feature flags as string variables in order to reference them from code:
public static class MyFeatureFlags
{
public const string FeatureA = "FeatureA";
public const string FeatureB = "FeatureB";
public const string FeatureC = "FeatureC";
}
Feature flag checks
A common pattern of feature management is to check if a feature flag is set to on and if so, run a section of code. For example:
IFeatureManager featureManager; ... if (await featureManager.IsEnabledAsync(MyFeatureFlags.FeatureA))
{
// Run the following code
}
Controller actions
With MVC controllers, you can use the FeatureGate
attribute to control whether a whole controller class or a specific action is enabled. The following HomeController
controller requires FeatureA
to be on before any action the controller class contains can be executed:
using Microsoft.FeatureManagement.Mvc; [FeatureGate(MyFeatureFlags.FeatureA)]
public class HomeController : Controller
{ ...
}
The following Index
action requires FeatureA
to be on before it can run:
using Microsoft.FeatureManagement.Mvc; [FeatureGate(MyFeatureFlags.FeatureA)]
public IActionResult Index()
{
return View();
}
When an MVC controller or action is blocked because the controlling feature flag is off, a registered IDisabledFeaturesHandler interface is called. The default IDisabledFeaturesHandler
interface returns a 404 status code to the client with no response body.
MVC views
Open _ViewImports.cshtml in the Views directory, and add the feature
manager ta@addTagHelper *, Microsoft.FeatureManagement.AspNetCoreg helper:
<feature name="FeatureA"> <p>This can only be seen if 'FeatureA' is enabled.</p> </feature>
To display alternate content when the requirements are not met the negate
attribute can be used.
<feature name="FeatureA" negate="true"> <p>This will be shown if 'FeatureA' is disabled.</p> </feature>
he feature <feature>
tag can also be used to show content if any or all features in a list are enabled.
<feature name="FeatureA, FeatureB" requirement="All">
<p>This can only be seen if 'FeatureA' and 'FeatureB' are enabled.</p> </feature> <feature name="FeatureA, FeatureB" requirement="Any">
<p>This can be seen if 'FeatureA', 'FeatureB', or both are enabled.</p> </feature>
MVC filters
You can set up MVC filters so that they’re activated based on the state of a feature flag. This capability is limited to filters that implement IAsyncActionFilter. The following code adds an MVC filter named ThirdPartyActionFilter
. This filter is triggered within the MVC pipeline only if FeatureA
is enabled.
using Microsoft.FeatureManagement.FeatureFilters;
IConfiguration Configuration { get; set;}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.AddForFeature<ThirdPartyActionFilter>(MyFeatureFlags.FeatureA);
});
You can also use feature flags to conditionally add application branches and middleware. The following code inserts a middleware component in the request pipeline only when FeatureA
is enabled:
app.UseMiddlewareForFeature<ThirdPartyMiddleware>(MyFeatureFlags.FeatureA);
app.UseMiddlewareForFeature<ThirdPartyMiddleware>(MyFeatureFlags.FeatureA);
In this post, you learned how to implement feature flags in your ASP.NET Core application by using the Microsoft.FeatureManagement
Conclusion
In this post I have described, using feature flags in an ASP.NET Core app, and how to setup feature management in your app. How to add feature flags in key parts of your application to control feature availability. Integrate with App Configuration when you’re using it to manage feature flags.