diff --git a/src/Microsoft.FeatureManagement.AspNetCore/FeatureGatedAsyncActionFilter.cs b/src/Microsoft.FeatureManagement.AspNetCore/FeatureGatedAsyncActionFilter.cs
index 80f540e8..073f953f 100644
--- a/src/Microsoft.FeatureManagement.AspNetCore/FeatureGatedAsyncActionFilter.cs
+++ b/src/Microsoft.FeatureManagement.AspNetCore/FeatureGatedAsyncActionFilter.cs
@@ -4,73 +4,33 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.FeatureManagement
{
///
- /// A place holder MVC filter that is used to dynamically activate a filter based on whether a feature (or set of features) is enabled.
+ /// A place holder MVC filter that is used to dynamically activate a filter based on whether a feature is enabled.
///
/// The filter that will be used instead of this placeholder.
class FeatureGatedAsyncActionFilter : IAsyncActionFilter where T : IAsyncActionFilter
{
- ///
- /// Creates a feature gated filter for multiple features with a specified requirement type and ability to negate the evaluation.
- ///
- /// Specifies whether all or any of the provided features should be enabled.
- /// Whether to negate the evaluation result.
- /// The features that control whether the wrapped filter executes.
- public FeatureGatedAsyncActionFilter(RequirementType requirementType, bool negate, params string[] features)
+ public FeatureGatedAsyncActionFilter(string featureName)
{
- if (features == null || features.Length == 0)
+ if (string.IsNullOrEmpty(featureName))
{
- throw new ArgumentNullException(nameof(features));
+ throw new ArgumentNullException(nameof(featureName));
}
- Features = features;
- RequirementType = requirementType;
- Negate = negate;
+ FeatureName = featureName;
}
- ///
- /// The set of features that gate the wrapped filter.
- ///
- public IEnumerable Features { get; }
-
- ///
- /// Controls whether any or all features in should be enabled to allow the wrapped filter to execute.
- ///
- public RequirementType RequirementType { get; }
-
- ///
- /// Negates the evaluation for whether or not the wrapped filter should execute.
- ///
- public bool Negate { get; }
+ public string FeatureName { get; }
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
- IFeatureManagerSnapshot featureManager = context.HttpContext.RequestServices.GetRequiredService();
-
- bool enabled;
-
- // Enabled state is determined by either 'any' or 'all' features being enabled.
- if (RequirementType == RequirementType.All)
- {
- enabled = await Features.All(async f => await featureManager.IsEnabledAsync(f).ConfigureAwait(false));
- }
- else
- {
- enabled = await Features.Any(async f => await featureManager.IsEnabledAsync(f).ConfigureAwait(false));
- }
-
- if (Negate)
- {
- enabled = !enabled;
- }
+ IFeatureManager featureManager = context.HttpContext.RequestServices.GetRequiredService();
- if (enabled)
+ if (await featureManager.IsEnabledAsync(FeatureName).ConfigureAwait(false))
{
IAsyncActionFilter filter = ActivatorUtilities.CreateInstance(context.HttpContext.RequestServices);
diff --git a/src/Microsoft.FeatureManagement.AspNetCore/FilterCollectionExtensions.cs b/src/Microsoft.FeatureManagement.AspNetCore/FilterCollectionExtensions.cs
index 86bece85..cfd46554 100644
--- a/src/Microsoft.FeatureManagement.AspNetCore/FilterCollectionExtensions.cs
+++ b/src/Microsoft.FeatureManagement.AspNetCore/FilterCollectionExtensions.cs
@@ -16,63 +16,12 @@ public static class FilterCollectionExtensions
/// The MVC filter to add and use if the feature is enabled.
/// The filter collection to add to.
/// The feature that will need to enabled to trigger the execution of the MVC filter.
- /// The reference to the added filter metadata.
+ ///
public static IFilterMetadata AddForFeature(this FilterCollection filters, string feature) where TFilterType : IAsyncActionFilter
{
- IFilterMetadata filterMetadata = new FeatureGatedAsyncActionFilter(RequirementType.Any, false, feature);
+ IFilterMetadata filterMetadata = null;
- filters.Add(filterMetadata);
-
- return filterMetadata;
- }
-
- ///
- /// Adds an MVC filter that will only activate during a request if the specified feature is enabled.
- ///
- /// The MVC filter to add and use if the feature is enabled.
- /// The filter collection to add to.
- /// The features that control whether the MVC filter executes.
- /// The reference to the added filter metadata.
- public static IFilterMetadata AddForFeature(this FilterCollection filters, params string[] features) where TFilterType : IAsyncActionFilter
- {
- IFilterMetadata filterMetadata = new FeatureGatedAsyncActionFilter(RequirementType.Any, false, features);
-
- filters.Add(filterMetadata);
-
- return filterMetadata;
- }
-
- ///
- /// Adds an MVC filter that will only activate during a request if the specified features are enabled based on the provided requirement type.
- ///
- /// The MVC filter to add and use if the features condition is satisfied.
- /// The filter collection to add to.
- /// Specifies whether all or any of the provided features should be enabled.
- /// The features that control whether the MVC filter executes.
- /// The reference to the added filter metadata.
- public static IFilterMetadata AddForFeature(this FilterCollection filters, RequirementType requirementType, params string[] features) where TFilterType : IAsyncActionFilter
- {
- IFilterMetadata filterMetadata = new FeatureGatedAsyncActionFilter(requirementType, false, features);
-
- filters.Add(filterMetadata);
-
- return filterMetadata;
- }
-
- ///
- /// Adds an MVC filter that will only activate during a request if the specified features are enabled based on the provided requirement type and negation flag.
- ///
- /// The MVC filter to add and use if the features condition is satisfied.
- /// The filter collection to add to.
- /// Specifies whether all or any of the provided features should be enabled.
- /// Whether to negate the evaluation result for the provided features set.
- /// The features that control whether the MVC filter executes.
- /// The reference to the added filter metadata.
- public static IFilterMetadata AddForFeature(this FilterCollection filters, RequirementType requirementType, bool negate, params string[] features) where TFilterType : IAsyncActionFilter
- {
- IFilterMetadata filterMetadata = new FeatureGatedAsyncActionFilter(requirementType, negate, features);
-
- filters.Add(filterMetadata);
+ filters.Add(new FeatureGatedAsyncActionFilter(feature));
return filterMetadata;
}
diff --git a/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj b/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj
index 42b212cb..12aa4e1f 100644
--- a/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj
+++ b/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj
@@ -5,7 +5,7 @@
4
- 4
+ 3
0
diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj
index 97dbb7ff..d195cfa8 100644
--- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj
+++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj
@@ -4,7 +4,7 @@
4
- 4
+ 3
0
diff --git a/src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs b/src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs
index 389d129f..62b5f4a5 100644
--- a/src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs
+++ b/src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs
@@ -37,9 +37,9 @@ public TimeWindowFilter(ILoggerFactory loggerFactory = null)
public IMemoryCache Cache { get; set; }
///
- /// This property allows the time window filter to use custom .
+ /// This property allows the time window filter in our test suite to use simulated time.
///
- public TimeProvider SystemClock { get; set; }
+ internal TimeProvider SystemClock { get; set; }
///
/// Binds configuration representing filter parameters to .
diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs
index 3c043152..12004aea 100644
--- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs
+++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs
@@ -103,7 +103,7 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke
//
// First, check local cache
- if (_variantCache.ContainsKey(cacheKey))
+ if (_variantCache.ContainsKey(feature))
{
return _variantCache[cacheKey];
}
@@ -121,7 +121,7 @@ public async ValueTask GetVariantAsync(string feature, ITargetingContex
//
// First, check local cache
- if (_variantCache.ContainsKey(cacheKey))
+ if (_variantCache.ContainsKey(feature))
{
return _variantCache[cacheKey];
}
diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj
index e815b031..560a6c07 100644
--- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj
+++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj
@@ -5,7 +5,7 @@
4
- 4
+ 3
0
diff --git a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs
index db43e8c6..806c66fa 100644
--- a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs
+++ b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs
@@ -175,8 +175,7 @@ private static IFeatureManagementBuilder GetFeatureManagementBuilder(IServiceCol
builder.AddFeatureFilter(sp =>
new TimeWindowFilter()
{
- Cache = sp.GetRequiredService(),
- SystemClock = sp.GetService() ?? TimeProvider.System,
+ Cache = sp.GetRequiredService()
});
builder.AddFeatureFilter();
diff --git a/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs b/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs
index 8bfd5ec4..68b7efc1 100644
--- a/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs
+++ b/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs
@@ -202,47 +202,6 @@ public async Task GatesRazorPageFeatures()
Assert.Equal(HttpStatusCode.OK, gateAnyNegateResponse.StatusCode);
}
- [Fact]
- public async Task GatesActionFilterFeatures()
- {
- IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
-
- TestServer server = new TestServer(WebHost.CreateDefaultBuilder().ConfigureServices(services =>
- {
- services
- .AddSingleton(config)
- .AddFeatureManagement()
- .AddFeatureFilter();
-
- services.AddMvcCore(o =>
- {
- DisableEndpointRouting(o);
- o.Filters.AddForFeature(RequirementType.All, Features.ConditionalFeature, Features.ConditionalFeature2);
- });
- }).Configure(app => app.UseMvc()));
-
- TestFilter filter = (TestFilter)server.Host.Services.GetRequiredService>().First(f => f is TestFilter);
- HttpClient client = server.CreateClient();
-
- //
- // Enable all features
- filter.Callback = _ => Task.FromResult(true);
- HttpResponseMessage res = await client.GetAsync("");
- Assert.True(res.Headers.Contains(nameof(MvcFilter)));
-
- //
- // Enable 1/2 features
- filter.Callback = ctx => Task.FromResult(ctx.FeatureName == Features.ConditionalFeature);
- res = await client.GetAsync("");
- Assert.False(res.Headers.Contains(nameof(MvcFilter)));
-
- //
- // Enable no
- filter.Callback = _ => Task.FromResult(false);
- res = await client.GetAsync("");
- Assert.False(res.Headers.Contains(nameof(MvcFilter)));
- }
-
private static void DisableEndpointRouting(MvcOptions options)
{
options.EnableEndpointRouting = false;
diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs
index 10636b84..1b8ae34b 100644
--- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs
+++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs
@@ -332,67 +332,6 @@ public async Task ThreadSafeSnapshot()
}
}
- [Fact]
- public async Task ReturnsCachedResultFromSnapshot()
- {
- IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
-
- var services = new ServiceCollection();
-
- services
- .AddSingleton(config)
- .AddFeatureManagement()
- .AddFeatureFilter();
-
- ServiceProvider serviceProvider = services.BuildServiceProvider();
-
- IVariantFeatureManager featureManager = serviceProvider.GetRequiredService();
-
- IVariantFeatureManager featureManagerSnapshot = serviceProvider.GetRequiredService();
-
- IEnumerable featureFilters = serviceProvider.GetRequiredService>();
-
- TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter);
-
- int callCount = 0;
- bool filterEnabled = true;
-
- testFeatureFilter.Callback = (evaluationContext) =>
- {
- callCount++;
- return Task.FromResult(filterEnabled);
- };
-
- // First evaluation - filter is enabled and should return true
- bool result1 = await featureManagerSnapshot.IsEnabledAsync(Features.ConditionalFeature);
- Assert.Equal(1, callCount);
- Assert.True(result1);
-
- Variant variant1 = await featureManagerSnapshot.GetVariantAsync(Features.ConditionalFeature);
- Assert.Equal(2, callCount);
- Assert.Equal("DefaultWhenEnabled", variant1.Name);
-
- // "Shut down" the feature filter
- filterEnabled = false;
-
- // Second evaluation - should use cached value despite filter being shut down
- bool result2 = await featureManagerSnapshot.IsEnabledAsync(Features.ConditionalFeature);
- Assert.Equal(2, callCount);
- Assert.True(result2);
-
- Variant variant2 = await featureManagerSnapshot.GetVariantAsync(Features.ConditionalFeature);
- Assert.Equal(2, callCount);
- Assert.Equal("DefaultWhenEnabled", variant2.Name);
-
- bool result3 = await featureManager.IsEnabledAsync(Features.ConditionalFeature);
- Assert.Equal(3, callCount);
- Assert.False(result3);
-
- Variant variant3 = await featureManager.GetVariantAsync(Features.ConditionalFeature);
- Assert.Equal(4, callCount);
- Assert.Equal("DefaultWhenDisabled", variant3.Name);
- }
-
[Fact]
public void AddsScopedFeatureManagement()
{
@@ -584,20 +523,6 @@ public async Task MergesFeatureFlagsFromDifferentConfigurationSources()
Assert.True(await featureManager8.IsEnabledAsync("FeatureC"));
Assert.False(await featureManager8.IsEnabledAsync("Feature1"));
Assert.False(await featureManager8.IsEnabledAsync("Feature2"));
-
- var configurationManager = new ConfigurationManager();
- configurationManager
- .AddJsonFile("appsettings1.json")
- .AddJsonFile("appsettings2.json");
-
- var services = new ServiceCollection();
- services.AddFeatureManagement();
-
- var featureManager9 = new FeatureManager(new ConfigurationFeatureDefinitionProvider(configurationManager, mergeOptions));
- Assert.True(await featureManager9.IsEnabledAsync("FeatureA"));
- Assert.True(await featureManager9.IsEnabledAsync("FeatureB"));
- Assert.True(await featureManager9.IsEnabledAsync("Feature1"));
- Assert.False(await featureManager9.IsEnabledAsync("Feature2")); // appsettings2 should override appsettings1
}
}
diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json
index 018ef5c4..e192a5ff 100644
--- a/tests/Tests.FeatureManagement/appsettings.json
+++ b/tests/Tests.FeatureManagement/appsettings.json
@@ -28,18 +28,6 @@
}
}
]
- },
- "variants": [
- {
- "name": "DefaultWhenEnabled"
- },
- {
- "name": "DefaultWhenDisabled"
- }
- ],
- "allocation": {
- "default_when_enabled": "DefaultWhenEnabled",
- "default_when_disabled": "DefaultWhenDisabled"
}
},
{