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

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


🧘‍♂️ Like this kind of content?

Follow my dev blog → ZenOfCode

Or drop me a follow here on Dev.to 💬