Feature Flags in .NET: A Minimal API with Toggle Power (Using Microsoft.FeatureManagement)
Feature flags give you fine-grained control over your application’s behavior—turn features on or off, run experiments, and roll back instantly without redeploying. In this tutorial, you’ll integrate the first‑party Microsoft.FeatureManagement
package into a .NET Minimal API, source flags from configuration and a custom provider, and explore both attribute- and code-based gating.
Why Use Microsoft.FeatureManagement?
- First‑party support: Official Microsoft package, integrates with .NET configuration and dependency injection.
- Flexible filters: Time windows, percentages, custom logic.
- Clean architecture alignment: Business logic stays decoupled from flag storage.
Table of Contents
- Project Setup
- Define Feature Flags in Configuration
- Wire Up Microsoft.FeatureManagement
- Use Flags in Endpoints
- FeatureGate Attributes
- Custom Feature Provider
- Testing
- References
Project Setup
dotnet new webapi -n FlagsWithMicrosoft
cd FlagsWithMicrosoft
Edit your .csproj
to target .NET 6+ and remove the default Weather sample. Then install the feature management package:
dotnet add package Microsoft.FeatureManagement
Define Feature Flags in Configuration
Using appsettings.json
, declare flags under a FeatureManagement
section. This keeps flags in config, supporting environment-based overrides.
// appsettings.json
{
"FeatureManagement": {
"NewUI": true,
"BetaEndpoint": false,
"TimeLimitedFeature": {
"EnabledFor": [
{ "Name": "TimeWindow", "Parameters": { "Start": "2025-05-01", "End": "2025-06-01" } }
]
}
}
}
- NewUI: simple on/off.
- BetaEndpoint: off by default.
- TimeLimitedFeature: auto‑enabled during a date range.
Wire Up Microsoft.FeatureManagement
In Program.cs
, register feature management services:
using Microsoft.FeatureManagement;
var builder = WebApplication.CreateBuilder(args);
// Add feature management and built-in filters
builder.Services.AddFeatureManagement()
.AddFeatureFilter<Microsoft.FeatureManagement.FeatureFilters.TimeWindowFilter>();
var app = builder.Build();
This reads FeatureManagement
from configuration and enables the built‑in TimeWindowFilter.
Use Flags in Endpoints
You can check flags manually using IFeatureManagerSnapshot
:
using Microsoft.FeatureManagement;
app.MapGet("/new-ui-data", async ([FromServices] IFeatureManagerSnapshot fm) =>
{
if (!await fm.IsEnabledAsync("NewUI"))
return Results.NotFound("New UI not available.");
return Results.Ok(new { message = "Welcome to the new UI!" });
});
Above: manual check with IsEnabledAsync()
.
FeatureGate Attributes
For attribute-based gating, add [FeatureGate]
on endpoints or controllers. This automatically returns 404 if the feature is disabled.
using Microsoft.FeatureManagement;
// Attribute on Minimal API endpoint
app.MapGet("/beta-attr", () => "Beta data via attribute")
.RequireFeature("BetaEndpoint");
// In MVC controller
[ApiController]
[Route("api/[controller]")]
public class ReportsController : ControllerBase
{
[HttpGet("special")]
[FeatureGate("TimeLimitedFeature")]
public IActionResult GetTimeLimitedReport()
{
return Ok("Time-limited report data");
}
}
-
.RequireFeature("BetaEndpoint")
for Minimal APIs -
[FeatureGate("TimeLimitedFeature")]
for controllers
Custom Feature Provider
Implement IFeatureDefinitionProvider
to fetch flags from an external service.
// Infrastructure/ExternalFeatureProvider.cs
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.FeatureDefinitions;
public class ExternalFeatureProvider : IFeatureDefinitionProvider
{
private readonly IExternalFlagRepository _repo;
public ExternalFeatureProvider(IExternalFlagRepository repo) => _repo = repo;
public async Task<FeatureDefinition> GetFeatureDefinitionAsync(string featureName)
{
var flag = await _repo.FetchFlagAsync(featureName);
return new FeatureDefinition(
name: featureName,
enabledFor: flag.Enabled
? new[] { new FeatureFilterConfiguration("AlwaysOn") }
: Array.Empty<FeatureFilterConfiguration>());
}
public async Task<IEnumerable<FeatureDefinition>> GetAllFeatureDefinitionsAsync()
{
var flags = await _repo.FetchAllFlagsAsync();
return flags.Select(f => new FeatureDefinition(
f.Name,
f.Enabled ? new[] { new FeatureFilterConfiguration("AlwaysOn") } : Array.Empty<FeatureFilterConfiguration>()));
}
}
// Infrastructure/ExternalFlagRepository.cs
public interface IExternalFlagRepository
{
Task<FlagDto> FetchFlagAsync(string name);
Task<IEnumerable<FlagDto>> FetchAllFlagsAsync();
}
public class InMemoryExternalFlagRepo : IExternalFlagRepository
{
private readonly Dictionary<string,bool> _store = new() { ["NewUI"] = true, ["BetaEndpoint"] = false };
public Task<FlagDto> FetchFlagAsync(string name) =>
Task.FromResult(new FlagDto { Name = name, Enabled = _store.GetValueOrDefault(name) });
public Task<IEnumerable<FlagDto>> FetchAllFlagsAsync() =>
Task.FromResult(_store.Select(kv => new FlagDto { Name = kv.Key, Enabled = kv.Value }));
}
public record FlagDto { public string Name { get; init; } public bool Enabled { get; init; } }
Register in Program.cs
:
builder.Services.AddSingleton<IExternalFlagRepository, InMemoryExternalFlagRepo>();
builder.Services.AddSingleton<IFeatureDefinitionProvider, ExternalFeatureProvider>();
Testing
-
Unit test attribute endpoints by mocking
IFeatureManagerSnapshot
. -
Integration test custom provider by seeding
InMemoryExternalFlagRepo
.
Example xUnit test for manual check:
[Fact]
public async Task NewUI_ReturnsNotFound_WhenFlagOff()
{
var fm = new Mock<IFeatureManagerSnapshot>();
fm.Setup(x => x.IsEnabledAsync("NewUI", It.IsAny<CancellationToken>())).ReturnsAsync(false);
var result = await new UIEndpoint(fm.Object).Get();
Assert.IsType<NotFoundObjectResult>(result);
}
Feature flags give you the serenity to release often and safely. Stay Zen.
References
- Official docs: https://learn.microsoft.com/en-gb/azure/azure-app-configuration/feature-management-dotnet-reference
- GitHub repo: https://github.com/microsoft/FeatureManagement-Dotnet
🧘♂️ Like this kind of content?
Follow my dev blog → ZenOfCode
Or drop me a follow here on Dev.to 💬