From 8be45cdd629a14605ceca5c9581703e389b8d24e Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 25 Sep 2024 12:22:31 -0700 Subject: [PATCH 1/3] Resolves snapshot breaking change and adjusts feature tag helper as well --- .../TagHelpers/FeatureTagHelper.cs | 11 +- .../FeatureManagerSnapshot.cs | 47 +++-- .../ServiceCollectionExtensions.cs | 94 ++++------ .../FeatureManagementAspNetCore.cs | 69 ++++++++ .../Tests.FeatureManagement.AspNetCore.csproj | 1 + .../FeatureManagementTest.cs | 164 ++++++++++++------ .../Tests.FeatureManagement.csproj | 1 + 7 files changed, 260 insertions(+), 127 deletions(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs index f65e62e8..2cc897b8 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs @@ -14,7 +14,8 @@ namespace Microsoft.FeatureManagement.Mvc.TagHelpers /// public class FeatureTagHelper : TagHelper { - private readonly IVariantFeatureManager _featureManager; + private readonly IFeatureManager _featureManager; + private readonly IVariantFeatureManager _variantFeatureManager; /// /// A feature name, or comma separated list of feature names, for which the content should be rendered. By default, all specified features must be enabled to render the content, but this requirement can be controlled by the property. @@ -38,12 +39,14 @@ public class FeatureTagHelper : TagHelper public string Variant { get; set; } /// - /// Creates a feature tag helper. + /// Creates a feature tag helper. Takes both a feature manager and a variant feature manager for backwards compatibility. /// /// The feature manager snapshot to use to evaluate feature state. - public FeatureTagHelper(IVariantFeatureManagerSnapshot featureManager) + /// The variant feature manager snapshot to use to evaluate feature state. + public FeatureTagHelper(IFeatureManagerSnapshot featureManager, IVariantFeatureManagerSnapshot variantFeatureManager) { _featureManager = featureManager ?? throw new ArgumentNullException(nameof(featureManager)); + _variantFeatureManager = variantFeatureManager ?? throw new ArgumentNullException(nameof(variantFeatureManager)); } /// @@ -84,7 +87,7 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu enabled = await variants.Any( async variant => { - Variant assignedVariant = await _featureManager.GetVariantAsync(features.First()).ConfigureAwait(false); + Variant assignedVariant = await _variantFeatureManager.GetVariantAsync(features.First()).ConfigureAwait(false); return variant == assignedVariant?.Name; }); diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index 384bb2b1..a324ca6a 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -16,19 +16,38 @@ namespace Microsoft.FeatureManagement /// class FeatureManagerSnapshot : IFeatureManagerSnapshot, IVariantFeatureManagerSnapshot { - private readonly IVariantFeatureManager _featureManager; - private readonly ConcurrentDictionary> _flagCache = new ConcurrentDictionary>(); + private readonly IFeatureManager _featureManager; + private readonly IVariantFeatureManager _variantFeatureManager; + private readonly ConcurrentDictionary> _flagCache = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _variantFlagCache = new ConcurrentDictionary>(); private readonly ConcurrentDictionary _variantCache = new ConcurrentDictionary(); private IEnumerable _featureNames; - public FeatureManagerSnapshot(IVariantFeatureManager featureManager) + // Takes both a feature manager and a variant feature manager for backwards compatibility. + public FeatureManagerSnapshot(IFeatureManager featureManager, IVariantFeatureManager variantFeatureManager) { _featureManager = featureManager ?? throw new ArgumentNullException(nameof(featureManager)); + _variantFeatureManager = variantFeatureManager ?? throw new ArgumentNullException(nameof(variantFeatureManager)); } - public IAsyncEnumerable GetFeatureNamesAsync() + public async IAsyncEnumerable GetFeatureNamesAsync() { - return GetFeatureNamesAsync(CancellationToken.None); + if (_featureNames == null) + { + var featureNames = new List(); + + await foreach (string featureName in _featureManager.GetFeatureNamesAsync().ConfigureAwait(false)) + { + featureNames.Add(featureName); + } + + _featureNames = featureNames; + } + + foreach (string featureName in _featureNames) + { + yield return featureName; + } } public async IAsyncEnumerable GetFeatureNamesAsync([EnumeratorCancellation] CancellationToken cancellationToken) @@ -37,7 +56,7 @@ public async IAsyncEnumerable GetFeatureNamesAsync([EnumeratorCancellati { var featureNames = new List(); - await foreach (string featureName in _featureManager.GetFeatureNamesAsync(cancellationToken).ConfigureAwait(false)) + await foreach (string featureName in _variantFeatureManager.GetFeatureNamesAsync(cancellationToken).ConfigureAwait(false)) { featureNames.Add(featureName); } @@ -55,28 +74,28 @@ public Task IsEnabledAsync(string feature) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, CancellationToken.None)).AsTask(); + (key) => _featureManager.IsEnabledAsync(key)); } public Task IsEnabledAsync(string feature, TContext context) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, context, CancellationToken.None)).AsTask(); + (key) => _featureManager.IsEnabledAsync(key, context)); } public ValueTask IsEnabledAsync(string feature, CancellationToken cancellationToken) { - return _flagCache.GetOrAdd( + return _variantFlagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, cancellationToken)); + (key) => _variantFeatureManager.IsEnabledAsync(key, cancellationToken)); } public ValueTask IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken) { - return _flagCache.GetOrAdd( + return _variantFlagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, context, cancellationToken)); + (key) => _variantFeatureManager.IsEnabledAsync(key, context, cancellationToken)); } public async ValueTask GetVariantAsync(string feature, CancellationToken cancellationToken) @@ -90,7 +109,7 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke return _variantCache[cacheKey]; } - Variant variant = await _featureManager.GetVariantAsync(feature, cancellationToken).ConfigureAwait(false); + Variant variant = await _variantFeatureManager.GetVariantAsync(feature, cancellationToken).ConfigureAwait(false); _variantCache[cacheKey] = variant; @@ -108,7 +127,7 @@ public async ValueTask GetVariantAsync(string feature, ITargetingContex return _variantCache[cacheKey]; } - Variant variant = await _featureManager.GetVariantAsync(feature, context, cancellationToken).ConfigureAwait(false); + Variant variant = await _variantFeatureManager.GetVariantAsync(feature, context, cancellationToken).ConfigureAwait(false); _variantCache[cacheKey] = variant; diff --git a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs index df7d3d00..602dd20c 100644 --- a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs +++ b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs @@ -34,13 +34,7 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec "Scoped feature management has been registered."); } - services.AddLogging(); - - services.AddMemoryCache(); - - // - // Add required services - services.TryAddSingleton(); + AddCommonFeatureManagementServices(services); services.AddSingleton(sp => new FeatureManager( sp.GetRequiredService(), @@ -58,27 +52,7 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec services.TryAddSingleton(sp => sp.GetRequiredService()); - services.AddScoped(); - - services.TryAddScoped(sp => sp.GetRequiredService()); - - services.TryAddScoped(sp => sp.GetRequiredService()); - - var builder = new FeatureManagementBuilder(services); - - // - // Add built-in feature filters - builder.AddFeatureFilter(); - - builder.AddFeatureFilter(sp => - new TimeWindowFilter() - { - Cache = sp.GetRequiredService() - }); - - builder.AddFeatureFilter(); - - return builder; + return GetFeatureManagementBuilder(services); } /// @@ -120,13 +94,7 @@ public static IFeatureManagementBuilder AddScopedFeatureManagement(this IService "Singleton feature management has been registered."); } - services.AddLogging(); - - services.AddMemoryCache(); - - // - // Add required services - services.TryAddSingleton(); + AddCommonFeatureManagementServices(services); services.AddScoped(sp => new FeatureManager( sp.GetRequiredService(), @@ -144,27 +112,7 @@ public static IFeatureManagementBuilder AddScopedFeatureManagement(this IService services.TryAddScoped(sp => sp.GetRequiredService()); - services.AddScoped(); - - services.TryAddScoped(sp => sp.GetRequiredService()); - - services.TryAddScoped(sp => sp.GetRequiredService()); - - var builder = new FeatureManagementBuilder(services); - - // - // Add built-in feature filters - builder.AddFeatureFilter(); - - builder.AddFeatureFilter(sp => - new TimeWindowFilter() - { - Cache = sp.GetRequiredService() - }); - - builder.AddFeatureFilter(); - - return builder; + return GetFeatureManagementBuilder(services); } /// @@ -190,5 +138,39 @@ public static IFeatureManagementBuilder AddScopedFeatureManagement(this IService return services.AddScopedFeatureManagement(); } + + private static void AddCommonFeatureManagementServices(IServiceCollection services) + { + services.AddLogging(); + + services.AddMemoryCache(); + + services.TryAddSingleton(); + + services.AddScoped(); + + services.TryAddScoped(sp => sp.GetRequiredService()); + + services.TryAddScoped(sp => sp.GetRequiredService()); + } + + private static IFeatureManagementBuilder GetFeatureManagementBuilder(IServiceCollection services) + { + var builder = new FeatureManagementBuilder(services); + + // + // Add built-in feature filters + builder.AddFeatureFilter(); + + builder.AddFeatureFilter(sp => + new TimeWindowFilter() + { + Cache = sp.GetRequiredService() + }); + + builder.AddFeatureFilter(); + + return builder; + } } } diff --git a/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs b/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs index dce12cfe..79352588 100644 --- a/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs +++ b/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs @@ -5,10 +5,12 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.FeatureManagement; +using Microsoft.FeatureManagement.Mvc.TagHelpers; using System.Collections.Generic; using System.Linq; using System.Net; @@ -181,4 +183,71 @@ private static void DisableEndpointRouting(MvcOptions options) options.EnableEndpointRouting = false; } } + + public class CustomImplementationsFeatureManagementTests + { + public class CustomIFeatureManager : IFeatureManager + { + public IAsyncEnumerable GetFeatureNamesAsync() + { + return new string[1] { "Test" }.ToAsyncEnumerable(); + } + + public async Task IsEnabledAsync(string feature) + { + return await Task.FromResult(feature == "Test"); + } + + public async Task IsEnabledAsync(string feature, TContext context) + { + return await Task.FromResult(feature == "Test"); + } + } + + [Fact] + public async Task CustomIFeatureManagerAspNetCoreTest() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + var services = new ServiceCollection(); + + services.AddSingleton(config) + .AddSingleton() + .AddFeatureManagement(); // Shouldn't override + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + Assert.True(await featureManager.IsEnabledAsync("Test")); + Assert.False(await featureManager.IsEnabledAsync("NotTest")); + + // FeatureTagHelper should use available IFeatureManager + FeatureTagHelper featureTagHelper = new FeatureTagHelper(serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()); + TagHelperOutput tagHelperOutput = new TagHelperOutput("TestTag", new TagHelperAttributeList(), (aBool, aHtmlEncoder) => { return null; }); + + // Test returns true, so it shouldn't be modified + featureTagHelper.Name = "Test"; + Assert.False(tagHelperOutput.IsContentModified); + await featureTagHelper.ProcessAsync(null, tagHelperOutput); + Assert.False(tagHelperOutput.IsContentModified); + + tagHelperOutput.Reinitialize("TestTag", TagMode.StartTagAndEndTag); + + // NotTest returns false, so it should be modified + featureTagHelper.Name = "NotTest"; + Assert.False(tagHelperOutput.IsContentModified); + await featureTagHelper.ProcessAsync(null, tagHelperOutput); + Assert.True(tagHelperOutput.IsContentModified); + + tagHelperOutput.Reinitialize("TestTag", TagMode.StartTagAndEndTag); + + // When variant is used, Test flag should no longer exist and return false + featureTagHelper.Name = "Test"; + featureTagHelper.Variant = "Something"; + Assert.False(tagHelperOutput.IsContentModified); + await featureTagHelper.ProcessAsync(null, tagHelperOutput); + Assert.True(tagHelperOutput.IsContentModified); + } + } } diff --git a/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj b/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj index 72916ae8..398c1588 100644 --- a/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj +++ b/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj @@ -15,6 +15,7 @@ + diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs index d12fe51c..989fc11c 100644 --- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs +++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs @@ -375,59 +375,6 @@ public void AddsScopedFeatureManagement() Assert.Equal($"Singleton feature management has been registered.", ex.Message); } - [Fact] - public async Task CustomFeatureDefinitionProvider() - { - FeatureDefinition testFeature = new FeatureDefinition - { - Name = Features.ConditionalFeature, - EnabledFor = new List() - { - new FeatureFilterConfiguration - { - Name = "Test", - Parameters = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary() - { - { "P1", "V1" }, - }).Build() - } - } - }; - - var services = new ServiceCollection(); - - services.AddSingleton(new InMemoryFeatureDefinitionProvider(new FeatureDefinition[] { testFeature })) - .AddFeatureManagement() - .AddFeatureFilter(); - - ServiceProvider serviceProvider = services.BuildServiceProvider(); - - IFeatureManager featureManager = serviceProvider.GetRequiredService(); - - IEnumerable featureFilters = serviceProvider.GetRequiredService>(); - - // - // Sync filter - TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter); - - bool called = false; - - testFeatureFilter.Callback = (evaluationContext) => - { - called = true; - - Assert.Equal("V1", evaluationContext.Parameters["P1"]); - - Assert.Equal(Features.ConditionalFeature, evaluationContext.FeatureName); - - return Task.FromResult(true); - }; - - await featureManager.IsEnabledAsync(Features.ConditionalFeature); - - Assert.True(called); - } - [Fact] public async Task LastFeatureFlagWins() { @@ -1918,4 +1865,115 @@ public async Task TelemetryPublishing() Assert.True(result); } } + + public class CustomImplementationsFeatureManagementTests + { + public class CustomIFeatureManager : IFeatureManager + { + public IAsyncEnumerable GetFeatureNamesAsync() + { + return new string[1] { "Test" }.ToAsyncEnumerable(); + } + + public async Task IsEnabledAsync(string feature) + { + return await Task.FromResult(feature == "Test"); + } + + public async Task IsEnabledAsync(string feature, TContext context) + { + return await Task.FromResult(feature == "Test"); + } + } + + [Fact] + public async Task CustomIFeatureManagerTest() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + var services = new ServiceCollection(); + + services.AddSingleton(config) + .AddSingleton() + .AddFeatureManagement(); // Shouldn't override + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + Assert.True(await featureManager.IsEnabledAsync("Test")); + Assert.False(await featureManager.IsEnabledAsync("NotTest")); + + // Provider shouldn't be affected + IFeatureDefinitionProvider featureDefinitionProvider = serviceProvider.GetRequiredService(); + + Assert.True(await featureDefinitionProvider.GetAllFeatureDefinitionsAsync().AnyAsync()); + Assert.NotNull(await featureDefinitionProvider.GetFeatureDefinitionAsync("OnTestFeature")); + + // Snapshot should use available IFeatureManager + FeatureManagerSnapshot featureManagerSnapshot = serviceProvider.GetRequiredService(); + + Assert.True(await featureManagerSnapshot.IsEnabledAsync("Test")); + Assert.False(await featureManagerSnapshot.IsEnabledAsync("NotTest")); + Assert.False(await featureManagerSnapshot.IsEnabledAsync("OnTestFeature")); + + // But use IVariantFeatureManager when using new interface + Assert.False(await featureManagerSnapshot.IsEnabledAsync("Test", CancellationToken.None)); + Assert.False(await featureManagerSnapshot.IsEnabledAsync("NotTest", CancellationToken.None)); + Assert.True(await featureManagerSnapshot.IsEnabledAsync("OnTestFeature", CancellationToken.None)); + } + + [Fact] + public async Task CustomIFeatureDefinitionProvider() + { + FeatureDefinition testFeature = new FeatureDefinition + { + Name = Features.ConditionalFeature, + EnabledFor = new List() + { + new FeatureFilterConfiguration + { + Name = "Test", + Parameters = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary() + { + { "P1", "V1" }, + }).Build() + } + } + }; + + var services = new ServiceCollection(); + + services.AddSingleton(new InMemoryFeatureDefinitionProvider(new FeatureDefinition[] { testFeature })) + .AddFeatureManagement() + .AddFeatureFilter(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + IEnumerable featureFilters = serviceProvider.GetRequiredService>(); + + // + // Sync filter + TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter); + + bool called = false; + + testFeatureFilter.Callback = (evaluationContext) => + { + called = true; + + Assert.Equal("V1", evaluationContext.Parameters["P1"]); + + Assert.Equal(Features.ConditionalFeature, evaluationContext.FeatureName); + + return Task.FromResult(true); + }; + + await featureManager.IsEnabledAsync(Features.ConditionalFeature); + + Assert.True(called); + } + } } diff --git a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj index 829caf87..daf20490 100644 --- a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj +++ b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj @@ -10,6 +10,7 @@ + From ea8fea117c3de6b2e199b4764bf97b7e5b0482de Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 25 Sep 2024 15:34:13 -0700 Subject: [PATCH 2/3] Moves comment out of summary --- .../TagHelpers/FeatureTagHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs index 2cc897b8..cddcf129 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs @@ -39,12 +39,13 @@ public class FeatureTagHelper : TagHelper public string Variant { get; set; } /// - /// Creates a feature tag helper. Takes both a feature manager and a variant feature manager for backwards compatibility. + /// Creates a feature tag helper. /// /// The feature manager snapshot to use to evaluate feature state. /// The variant feature manager snapshot to use to evaluate feature state. public FeatureTagHelper(IFeatureManagerSnapshot featureManager, IVariantFeatureManagerSnapshot variantFeatureManager) { + // Takes both a feature manager and a variant feature manager for backwards compatibility. _featureManager = featureManager ?? throw new ArgumentNullException(nameof(featureManager)); _variantFeatureManager = variantFeatureManager ?? throw new ArgumentNullException(nameof(variantFeatureManager)); } From 3e075227074cb2975fcec7ed5125000b47805ec8 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 25 Sep 2024 16:37:44 -0700 Subject: [PATCH 3/3] Adjusted snapshot to use a shared IsEnabled cache --- .../FeatureManagerSnapshot.cs | 11 +++++------ .../Tests.FeatureManagement/FeatureManagementTest.cs | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index a324ca6a..12004aea 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -18,8 +18,7 @@ class FeatureManagerSnapshot : IFeatureManagerSnapshot, IVariantFeatureManagerSn { private readonly IFeatureManager _featureManager; private readonly IVariantFeatureManager _variantFeatureManager; - private readonly ConcurrentDictionary> _flagCache = new ConcurrentDictionary>(); - private readonly ConcurrentDictionary> _variantFlagCache = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _flagCache = new ConcurrentDictionary>(); private readonly ConcurrentDictionary _variantCache = new ConcurrentDictionary(); private IEnumerable _featureNames; @@ -74,26 +73,26 @@ public Task IsEnabledAsync(string feature) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key)); + (key) => new ValueTask(_featureManager.IsEnabledAsync(key))).AsTask(); } public Task IsEnabledAsync(string feature, TContext context) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, context)); + (key) => new ValueTask(_featureManager.IsEnabledAsync(key, context))).AsTask(); } public ValueTask IsEnabledAsync(string feature, CancellationToken cancellationToken) { - return _variantFlagCache.GetOrAdd( + return _flagCache.GetOrAdd( feature, (key) => _variantFeatureManager.IsEnabledAsync(key, cancellationToken)); } public ValueTask IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken) { - return _variantFlagCache.GetOrAdd( + return _flagCache.GetOrAdd( feature, (key) => _variantFeatureManager.IsEnabledAsync(key, context, cancellationToken)); } diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs index 989fc11c..af7d161a 100644 --- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs +++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs @@ -1917,10 +1917,10 @@ public async Task CustomIFeatureManagerTest() Assert.False(await featureManagerSnapshot.IsEnabledAsync("NotTest")); Assert.False(await featureManagerSnapshot.IsEnabledAsync("OnTestFeature")); - // But use IVariantFeatureManager when using new interface - Assert.False(await featureManagerSnapshot.IsEnabledAsync("Test", CancellationToken.None)); + // Use snapshot results even though IVariantFeatureManager would be called here + Assert.True(await featureManagerSnapshot.IsEnabledAsync("Test", CancellationToken.None)); Assert.False(await featureManagerSnapshot.IsEnabledAsync("NotTest", CancellationToken.None)); - Assert.True(await featureManagerSnapshot.IsEnabledAsync("OnTestFeature", CancellationToken.None)); + Assert.False(await featureManagerSnapshot.IsEnabledAsync("OnTestFeature", CancellationToken.None)); } [Fact]