Recently, I had the honor of contributing to the open-source project Microsoft/FeatureManagement-Dotnet, a well-established library that simplifies the use of feature flags within the .NET ecosystem. My implementation added native support for Minimal APIs in ASP.NET Core, providing a practical way to integrate dynamic feature control. In this article, I’ll explore this new functionality I developed, covering everything from basic concepts to advanced scenarios, with practical examples and suggestions on how to apply it in real-world projects.
The Role of Feature Flags in Modern Development
Feature flags, or functionality toggles, are a cornerstone of contemporary software development. They allow enabling or disabling features at runtime without requiring code changes or a new deployment. This is particularly valuable in cases such as:
- Progressive Rollouts: Gradually introducing new features to subsets of users, minimizing risks.
- A/B Testing: Comparing different versions of a feature to optimize outcomes.
- Access Control: Restricting features to specific users or groups based on dynamic rules.
- Experiment Management: Testing new ideas without compromising system stability.
The Microsoft.FeatureManagement library already provided robust support for feature flags in traditional ASP.NET Core controllers. With the introduction of Minimal APIs in .NET 6 — a lightweight and efficient model for building APIs — the need arose to adapt this capability. My contribution addresses this demand by integrating feature flags directly into the routing pipeline of Minimal APIs.
Introducing WithFeatureGate
The foundation of my implementation is the WithFeatureGate extension, a method designed to be simple and intuitive. It conditions endpoint execution on the state of feature flags. If the conditions aren’t met, the endpoint automatically returns an HTTP 404 (Not Found) status without executing the associated code.
A Basic Example
Here’s how it works in practice:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFeatureManagement();
var app = builder.Build();
app.MapGet("/test", () => "Feature Enabled")
.WithFeatureGate("TestFeature");
app.Run();
In this case, the /test endpoint is only accessible if the TestFeature feature flag is enabled. The configuration can be defined in appsettings.json like this:
{
"FeatureManagement": {
"TestFeature": true
}
}
If TestFeature is false, a GET request to /test returns a 404 without further processing. This integration reflects the simplicity of Minimal APIs while offering significant flexibility.
Advanced Features of the Implementation
The WithFeatureGate method goes beyond basic scenarios. It’s designed to support more complex use cases, providing detailed control over endpoint behavior. Below, I outline some of the key possibilities:
1. Control with Multiple Features
To require multiple feature flags to be active simultaneously, list their names:
app.MapGet("/test-multiple", () => "Multiple Features Enabled")
.WithFeatureGate("Feature1", "Feature2");
The /test-multiple endpoint only returns a 200 OK if both Feature1 and Feature2 are enabled. If either is disabled, the result is a 404. This is useful for features dependent on multiple components.
2. "Any" Requirement: Flexibility with Options
When the goal is to enable the endpoint with at least one active feature, use RequirementType.Any:
app.MapGet("/test-any", () => "Any Feature Enabled")
.WithFeatureGate(RequirementType.Any, "Feature1", "Feature2");
Here, the endpoint responds successfully if either Feature1 or Feature2 (or both) is active. This option is ideal for scenarios with alternative activation paths.
3. Negation: Inverted Logic
It’s possible to configure an endpoint to be accessible only when a feature is disabled:
app.MapGet("/test-negated", () => "Feature Denied")
.WithFeatureGate(true, "TestFeature");
In this example, /test-negated only works if TestFeature is false. This functionality enables inverted logic or alternative experiences.
4. Endpoint Groups with MapGroup
To apply a feature flag to a set of endpoints, the MapGroup method offers a practical solution:
var group = app.MapGroup("/api")
.WithFeatureGate("GroupFeature");
group.MapGet("/endpoint1", () => "Endpoint 1");
group.MapGet("/endpoint2", () => "Endpoint 2");
If GroupFeature is disabled, all endpoints under /api — such as /api/endpoint1 and /api/endpoint2 — return a 404. This approach simplifies managing entire API sections.
5. Targeting: Audience Control
The Microsoft.FeatureManagement library supports the TargetingFilter, which directs functionalities to specific users or groups based on detailed configurations. This is especially useful for personalization or segmented rollouts. Here’s how I implemented a practical example:
First, register the filter and configure an ITargetingContextAccessor in the service container. Below is an implementation that identifies the user and groups from the HTTP context:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFeatureManagement()
.AddFeatureFilter<TargetingFilter>();
builder.Services.AddSingleton<ITargetingContextAccessor, CustomTargetingContextAccessor>();
var app = builder.Build();
app.MapGet("/premium-feature", () => "Welcome to the Premium Feature!")
.WithFeatureGate("PremiumFeature");
app.Run();
// Implementation of ITargetingContextAccessor
public class CustomTargetingContextAccessor : ITargetingContextAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CustomTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public ValueTask<TargetingContext> GetContextAsync()
{
var context = _httpContextAccessor.HttpContext;
var userId = context?.User?.Identity?.Name ?? "anonymous";
var groups = context?.User?.Claims
.Where(c => c.Type == "group")
.Select(c => c.Value)
.ToArray() ?? Array.Empty<string>();
return new ValueTask<TargetingContext>(new TargetingContext
{
UserId = userId,
Groups = groups
});
}
}
In this example, the CustomTargetingContextAccessor uses the IHttpContextAccessor to extract the authenticated user’s name (via Identity.Name) and associated groups (via custom "group" claims). This assumes that authentication is configured, such as with JWT or cookies, and that the token includes groups as claims.
The configuration in appsettings.json defines who has access to the feature:
{
"FeatureManagement": {
"PremiumFeature": {
"EnabledFor": [
{
"Name": "Targeting",
"Parameters": {
"Audience": {
"Users": ["thaise.medeiros"],
"Groups": [
{
"Name": "PremiumUsers",
"RolloutPercentage": 100
}
]
}
}
}
]
}
}
}
Here, the /premium-feature endpoint is only available to the user "thaise.medeiros" or members of the "PremiumUsers" group. The RolloutPercentage field specifies that 100% of the group members have access, but this value can be adjusted for gradual rollouts — for example, 50% to reach half the group.
The ITargetingContextAccessor is key to connecting the configuration to actual requests. It provides the current user’s context, allowing the TargetingFilter to evaluate whether access should be granted. Combined with authentication (like JWT), this creates a robust and flexible way to tailor features for specific audiences.
Why Feature Flags make sense in Minimal APIs
Minimal APIs were designed for simplicity and performance, eliminating the complexity of controllers and unnecessary metadata. Integrating feature flags into the routing maintains this lean approach while adding essential dynamic control for modern applications. This shines in:
- Microservices: Managing distributed features becomes simpler.
- Continuous Delivery: Enables deploying code safely and activating features on demand.
- User Experience: Facilitates customizing the API for different profiles or tests.
Combining Minimal APIs with WithFeatureGate offers a straightforward development model paired with a powerful tool for software evolution.
How the functionality was implemented
Developing feature flag support for Minimal APIs required aligning the flexibility of FeatureManagement with the simplicity of Minimal APIs. The solution needed to fit naturally into the routing pipeline, leveraging ASP.NET Core’s endpoint filters to evaluate feature flags at runtime.
The implementation revolves around a filter that checks the state of flags before allowing or blocking access to endpoints. This filter is integrated into the Minimal APIs’ endpoint-building system through extensions that make its use seamless and intuitive. One challenge was ensuring it supported various configurations — such as multiple flags, "all active" or "at least one active" logic, and even negation.
Another critical aspect was integrating the filter with advanced features like targeting, which relies on a mechanism to identify the current user’s context. This was addressed using FeatureManagement’s existing infrastructure, ensuring compatibility with filters like TargetingFilter.
The result is a solution that extends FeatureManagement’s capabilities to Minimal APIs, balancing power and simplicity so developers can quickly adopt it in their projects.
Conclusion
Adding feature flag support to Minimal APIs with Microsoft.FeatureManagement was a significant contribution to the .NET community. With WithFeatureGate, it’s possible to implement everything from basic controls to advanced configurations like targeting and grouping, all practically and efficiently. Whether launching a new feature, conducting tests, or personalizing experiences, this implementation delivers the necessary tools elegantly.
The functionality is already available in the Microsoft/FeatureManagement-Dotnet repository, though it hasn’t yet been included in an official release. You can explore it in your projects now and see how feature flags can enhance software development and delivery — but it’ll be officially released soon!
Top comments (0)