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. All are annoying. Most are avoidable if you know where the sharp edges are.
Your Flag Names Are Strings Forever
Feature names are string literals. That means typos compile perfectly and fail at runtime in the most boring way possible.
You configure "NewDashboard" but check "NewDashbaord" in code, then spend an afternoon wondering why the shiny new UI never appears.
Workaround: centralize flag names in constants or a strongly-typed wrapper, and stop scattering raw strings throughout controllers, services, tag helpers, and filters.
Configuration Shape Is Less Forgiving Than It Looks
The library expects a specific configuration layout: either the FeatureManagement section with EnabledFor, or the newer Microsoft Feature Management schema with a feature_management section and a feature_flags array. If you typo the section name, mis-shape EnabledFor or conditions, or nest things oddly, flags will not behave as expected.
It usually looks like the system is “ignoring” your config when the real problem is structure.
Workaround: keep one canonical sample config in your repo, validate it in integration tests, and avoid creative JSON art projects in production.
Filter Behavior Depends on Context Quality
PercentageFilter is random per evaluation, so the same user may see different results across requests. That can be fine for coarse rollout, but it surprises teams expecting sticky behavior.
Workaround: if you need stable outcomes per user, use the built-in TargetingFilter with a DefaultRolloutPercentage instead of PercentageFilter. Register it with .WithTargeting(); in ASP.NET Core it uses the current principal identity as the user key, then hashes that value with the feature name for deterministic bucket assignment. If identity changes between requests, results change with it, and if identity is missing, every anonymous request lands in the same bucket.
IsEnabledAsync Everywhere Means You Can Re-Evaluate a Lot
Flag checks are asynchronous and easy to sprinkle everywhere. It feels clean until you realize you’re checking the same flag multiple times in one request path.
Workaround: evaluate once per request or operation, then pass the result through your flow. You get clearer behavior and fewer surprises.
IFeatureManager vs IFeatureManagerSnapshot
You can inject either interface, and that choice matters. In request-heavy applications, snapshot semantics are often what you actually want: consistency for the lifetime of a request.
Workaround: prefer IFeatureManagerSnapshot where consistent per-request behavior matters, and be intentional when using the non-snapshot manager.
None of This Is a Dealbreaker
Microsoft’s feature management library is still a great default choice in .NET. Most pain comes from naming drift, configuration mistakes, and context assumptions — not from the core library itself.
Know the quirks, add a few guardrails, and you can keep the benefits without the mystery bugs.
The framework is fine. Your future self just wants fewer typo-driven incidents.