Skip to content

Commit 8708158

Browse files
authored
Merge pull request #524 from crodriguesbr/add-feature-flags-endpoint-support
Add feature flags endpoint support
2 parents 87d2500 + e4e29e1 commit 8708158

3 files changed

Lines changed: 625 additions & 0 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Http.HttpResults;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using System;
9+
using System.Collections.Generic;
10+
using System.Linq;
11+
using System.Threading.Tasks;
12+
13+
namespace Microsoft.FeatureManagement.AspNetCore
14+
{
15+
/// <summary>
16+
/// An endpoint filter that controls access based on feature flag states.
17+
/// </summary>
18+
internal sealed class FeatureGateEndpointFilter : IEndpointFilter
19+
{
20+
/// <summary>
21+
/// Gets the collection of feature flags to evaluate.
22+
/// </summary>
23+
private readonly IEnumerable<string> _features;
24+
25+
/// <summary>
26+
/// Gets the type of requirement (All or Any) for feature evaluation.
27+
/// </summary>
28+
private readonly RequirementType _requirementType;
29+
30+
/// <summary>
31+
/// Gets whether the feature evaluation result should be negated.
32+
/// </summary>
33+
private readonly bool _negate;
34+
35+
/// <summary>
36+
/// Initializes a new instance of the <see cref="FeatureGateEndpointFilter"/> class.
37+
/// </summary>
38+
/// <param name="features">The collection of feature flags to evaluate.</param>
39+
/// <exception cref="ArgumentNullException">Thrown when features collection is null or empty.</exception>
40+
public FeatureGateEndpointFilter(params string[] features)
41+
: this(RequirementType.All, negate: false, features)
42+
{
43+
}
44+
45+
/// <summary>
46+
/// Creates a new instance of the <see cref="FeatureGateEndpointFilter"/> class.
47+
/// </summary>
48+
/// <param name="requirementType">Specifies whether all or any of the provided features should be enabled in order to pass.</param>
49+
/// <param name="features">The collection of feature flags to evaluate.</param>
50+
/// <exception cref="ArgumentNullException">Thrown when features collection is null or empty.</exception>
51+
public FeatureGateEndpointFilter(RequirementType requirementType, params string[] features)
52+
: this(requirementType, negate: false, features)
53+
{
54+
}
55+
56+
/// <summary>
57+
/// Creates a new instance of the <see cref="FeatureGateEndpointFilter"/> class.
58+
/// </summary>
59+
/// <param name="requirementType">Specifies whether all or any of the provided features should be enabled in order to pass.</param>
60+
/// <param name="negate">Specifies whether the feature evaluation result should be negated.</param>
61+
/// <param name="features">The collection of feature flags to evaluate.</param>
62+
/// <exception cref="ArgumentNullException">Thrown when features collection is null or empty.</exception>
63+
public FeatureGateEndpointFilter(RequirementType requirementType, bool negate, params string[] features)
64+
{
65+
if (features == null || features.Length == 0)
66+
{
67+
throw new ArgumentNullException(nameof(features));
68+
}
69+
70+
_features = features;
71+
_requirementType = requirementType;
72+
_negate = negate;
73+
}
74+
75+
/// <summary>
76+
/// Invokes the feature flag filter to control endpoint access based on feature states.
77+
/// </summary>
78+
/// <param name="context">The endpoint filter invocation context.</param>
79+
/// <param name="next">The delegate representing the next filter in the pipeline.</param>
80+
/// <returns>
81+
/// A <see cref="NotFound"/> result if access is denied, otherwise continues the pipeline.
82+
/// </returns>
83+
public async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
84+
{
85+
IVariantFeatureManager fm = context.HttpContext.RequestServices.GetRequiredService<IVariantFeatureManagerSnapshot>();
86+
87+
bool enabled = _requirementType == RequirementType.All
88+
? await _features.All(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false))
89+
: await _features.Any(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false));
90+
91+
bool isAllowed = _negate ? !enabled : enabled;
92+
93+
return isAllowed
94+
? await next(context).ConfigureAwait(false)
95+
: Results.NotFound();
96+
}
97+
}
98+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Http;
7+
8+
namespace Microsoft.FeatureManagement.AspNetCore
9+
{
10+
/// <summary>
11+
/// Extension methods that provide feature management integration for ASP.NET Core endpoint building.
12+
/// </summary>
13+
public static class FeatureGateEndpointFilterExtensions
14+
{
15+
/// <summary>
16+
/// Adds a filter to the endpoint that gates access based on whether one or more features are enabled.
17+
/// All features must be enabled for access to be granted.
18+
/// </summary>
19+
/// <param name="builder">The endpoint convention builder.</param>
20+
/// <param name="features">The collection of feature flags to evaluate.</param>
21+
/// <returns>The endpoint convention builder for chaining.</returns>
22+
public static IEndpointConventionBuilder WithFeatureGate(this IEndpointConventionBuilder builder, params string[] features)
23+
{
24+
return builder.AddEndpointFilter(new FeatureGateEndpointFilter(features));
25+
}
26+
27+
/// <summary>
28+
/// Adds a filter to the endpoint with specified requirement type for multiple features.
29+
/// </summary>
30+
/// <param name="builder">The endpoint convention builder.</param>
31+
/// <param name="requirementType">Specifies whether all or any of the provided features should be enabled in order to pass.</param>
32+
/// <param name="features">The collection of feature flags to evaluate.</param>
33+
/// <returns>The endpoint convention builder for chaining.</returns>
34+
public static IEndpointConventionBuilder WithFeatureGate(this IEndpointConventionBuilder builder, RequirementType requirementType, params string[] features)
35+
{
36+
return builder.AddEndpointFilter(new FeatureGateEndpointFilter(requirementType, features));
37+
}
38+
39+
/// <summary>
40+
/// Adds a filter to the endpoint with negation capability for multiple features.
41+
/// </summary>
42+
/// <param name="builder">The endpoint convention builder.</param>
43+
/// <param name="negate">Specifies whether the feature evaluation result should be negated.</param>
44+
/// <param name="features">The collection of feature flags to evaluate.</param>
45+
/// <returns>The endpoint convention builder for chaining.</returns>
46+
public static IEndpointConventionBuilder WithFeatureGate(this IEndpointConventionBuilder builder, bool negate, params string[] features)
47+
{
48+
return builder.AddEndpointFilter(new FeatureGateEndpointFilter(RequirementType.All, negate, features));
49+
}
50+
51+
/// <summary>
52+
/// Adds a filter to the endpoint with full control over requirement type and negation.
53+
/// </summary>
54+
/// <param name="builder">The endpoint convention builder.</param>
55+
/// <param name="requirementType">Specifies whether all or any of the provided features should be enabled in order to pass.</param>
56+
/// <param name="negate">Specifies whether the feature evaluation result should be negated.</param>
57+
/// <param name="features">The collection of feature flags to evaluate.</param>
58+
/// <returns>The endpoint convention builder for chaining.</returns>
59+
public static IEndpointConventionBuilder WithFeatureGate(this IEndpointConventionBuilder builder, RequirementType requirementType, bool negate, params string[] features)
60+
{
61+
return builder.AddEndpointFilter(new FeatureGateEndpointFilter(requirementType, negate, features));
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)