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 💬
Top comments (0)