microsoft / FeatureManagement-Dotnet

Microsoft.FeatureManagement provides standardized APIs for enabling feature flags within applications. Utilize this library to secure a consistent experience when developing applications that use patterns such as beta access, rollout, dark deployments, and more.
MIT License
1.02k stars 111 forks source link

Feature Request: AddEndpointFilter for Minimal API's #253

Open timdeschryver opened 1 year ago

timdeschryver commented 1 year ago

Hi, I was wondering if there are any plans to introduce an endpoint filter that can be used with Minimal API's.

While this library already provides the FeatureGate attribute for Controller based API's or MVC applications, I couldn't find anything about a helper method for Minimal API's.

It could be missing because this library existed before, ASP.NET Core introduced the new extension method AddEndpointFilter to customize a route handler in v7.

I think it can be a great addition to this library. Thanks in advance.

rossgrambo commented 1 year ago

Hello @timdeschryver ,

Looking into it a little now. From my understanding, the filter itself needs to handle the true and false case. Today, I believe it could be implemented like:

.AddEndpointFilter(async (context, next) =>
{
    if (await featureManager.IsEnabledAsync(FeatureName))
    {
         // true;
    } else {
        // false;
    }
});

If you care about context, similarly to FeatureGate, I believe you could define an ITargetingContextAccessor like this example. Or manually create the context in line like:

if (await featureManager.IsEnabledAsync(FeatureName, new TargetingContext{ User: context.username });

I hope those work for you. I don't yet see what a helper method could offer here. We could automatically handle the false case, but that would mean we're forcing everyone to that false result. If we pick 404, that might not be ideal for everyone's situation. Let me know if you have something different in mind!

timdeschryver commented 1 year ago

Thanks for the fast response @rossgrambo. That does indeed work, and we've implemented it in a similar way (also with a 404, to keep the same behavior as FeatureGate). I was just thinking that it could be useful to add this within the package, similar to FeatureGate.

Feel free to close this issue if this doesn't add much value.

rossgrambo commented 1 year ago

Ah, I see what you mean. I'll bring it up to the team as a suggestion. Thanks @timdeschryver!

vipwan commented 2 months ago

add EndpointFeatureMiddleware

public class EndpointFeatureMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IFeatureManager _featureManager;

    public EndpointFeatureMiddleware(RequestDelegate next, IFeatureManager featureManager)
    {
        _next = next;
        _featureManager = featureManager;
    }

    public async Task InvokeAsync(HttpContext context)
    {

        if (context.GetEndpoint() is { } endpoint)
        {
            if (endpoint.Metadata.GetMetadata<FeatureGateAttribute>() is { } metadata)
            {
                var features = metadata.Features;

                //只要有一个特性开启就可以:
                if (metadata.RequirementType == RequirementType.Any)
                {
                    foreach (var feature in features)
                    {
                        if (await _featureManager.IsEnabledAsync(feature))
                        {
                            await _next(context);
                            return;
                        }
                        context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                        return;
                    }
                }
                //所有特性都必须开启:
                else
                {
                    foreach (var feature in features)
                    {
                        if (!await _featureManager.IsEnabledAsync(feature))
                        {
                            context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                            return;
                        }
                    }
                }
            }
        }
        await _next(context);
    }
}
app.UseMiddleware<EndpointFeatureMiddleware>();

then test:

app.MapGet("feature", () =>
{
    return Results.Content("hello world");
}).WithMetadata(new FeatureGateAttribute("myfeature"));

It works fine :)