Microsoft.FeatureManagement is solid, widely used, and much better than building your own feature flag framework in a caffeine panic. But it has quirks. None are fatal. Most are avoidable if you know where the sharp edges are.
Your Flag Names Are Strings Forever
Feature names are strings. That means typos compile perfectly and fail at runtime in the most boring way possible. You configure:
{
"FeatureManagement": {
"NewDashboard": true
}
}
Then check:
await _featureManager.IsEnabledAsync("NewDashbaord");
Spot the typo? The compiler doesn't. Your shiny new UI never appears, and you spend an afternoon questioning your life choices.
The Fix
Centralize your flag names.
public static class FeatureFlags
{
public const string NewDashboard = "NewDashboard";
public const string ExperimentalSearch = "ExperimentalSearch";
}
Then use them everywhere:
await _featureManager.IsEnabledAsync(FeatureFlags.NewDashboard);
Or build a strongly typed wrapper if your application has enough flags to justify it. The important part is simple:
Stop scattering magic strings throughout your controllers, services, tag helpers, and filters.
Your typo budget is already being spent elsewhere.
Configuration Shape Is Less Forgiving Than It Looks
The library expects a specific configuration layout under FeatureManagement. If you typo the section name, misconfigure EnabledFor, or get creative with the JSON structure, your flags may not behave as expected. Then it looks like the framework is "ignoring" your configuration. It isn't. Your JSON is probably wrong.
The Fix
Keep one canonical configuration example in your repository. Test it. Validate important configuration through integration tests. And avoid turning appsettings.json into an experimental art project. A known-good example is much easier to maintain than debugging configuration structure at 4pm on a Friday.
Filter Behavior Depends on Context Quality
Percentage rollouts sound straightforward. Enable a feature for 20% of users. Easy. Except PercentageFilter evaluates randomly, so the same user can potentially get different results across requests. That's fine for coarse rollouts. It's less fine when you expect a user to consistently see the same version of a feature.
The Fix
If you need stable per-user behavior, use ConsistentPercentageFilter.
In ASP.NET Core scenarios, it can use the current principal identity as a stable user key and assign that user to a deterministic bucket. The catch? Your context still matters. If the user identity is missing or changes between requests, the results can still look random. So before blaming the feature flag library, make sure you're actually giving it a consistent identity.
Garbage context in. Confusing rollout behavior out.
IsEnabledAsync Everywhere Means You Can Re-Evaluate a Lot
Feature flag checks are asynchronous. They're also extremely easy to sprinkle throughout your application:
if (await _featureManager.IsEnabledAsync(FeatureFlags.NewDashboard))
{
// Do something
}
Then somewhere deeper in the same request:
if (await _featureManager.IsEnabledAsync(FeatureFlags.NewDashboard))
{
// Do something else
}
And again. And again. Before long, you're evaluating the same flag multiple times in one request path.
The Fix
When appropriate, evaluate the flag once and pass the result through the relevant operation.
var isNewDashboardEnabled =
await _featureManager.IsEnabledAsync(FeatureFlags.NewDashboard);
// Use isNewDashboardEnabled throughout this operation
This gives you clearer behavior and fewer opportunities for surprising changes between evaluations.
IFeatureManager vs. IFeatureManagerSnapshot
You can inject either IFeatureManager or IFeatureManagerSnapshot. And the choice matters. For request-heavy applications, snapshot semantics are often what you actually want. IFeatureManagerSnapshot provides consistency for the lifetime of a request or scope, so repeated checks can use the same evaluated state. That's useful when you don't want feature behavior changing halfway through an operation.
The Fix
Prefer IFeatureManagerSnapshot when consistent per-request behavior matters. Be intentional when using the non-snapshot manager. The important question isn't:
"Which interface compiles?"
They both do.
The question is:
"Could evaluating this flag differently during the same operation cause problems?"
If the answer is yes, snapshot semantics are probably worth considering.
None of This Is a Dealbreaker
Microsoft's feature management library is still a great default choice for .NET applications. Most of the pain comes from:
- Naming drift
- Configuration mistakes
- Context assumptions
- Repeated evaluations
- Not thinking about evaluation consistency
The framework is fine. Your future self just wants fewer typo-driven incidents. Add some guardrails, centralize your flag names, test your configuration, and understand how your filters and evaluation scope behave. Then you get the benefits of feature flags without quite as many mystery bugs.
And if you're tired of managing flags through configuration files, FeatureFlags.app gives you a central place to manage them without turning every flag change into an adventure.
Know the quirks. Add the guardrails. Ship anyway.
Top comments (0)