From 8f9a7e4052ac8113ea3834b527d4ec47db42032c Mon Sep 17 00:00:00 2001 From: Jimmy Campbell Date: Tue, 31 Aug 2021 14:15:28 -0400 Subject: [PATCH 01/10] Revert "Added default value for cancellation token in interfaces to keep existing usage possible. (#133)" (#138) This reverts commit c24f4fd8b72a006543641522a528eaa81bee4525. --- examples/ConsoleApp/FeatureFilters/AccountIdFilter.cs | 2 +- examples/FeatureFlagDemo/BrowserFilter.cs | 2 +- .../HttpContextTargetingContextAccessor.cs | 2 +- examples/FeatureFlagDemo/SuperUserFilter.cs | 2 +- .../ConfigurationFeatureDefinitionProvider.cs | 4 ++-- src/Microsoft.FeatureManagement/EmptySessionManager.cs | 4 ++-- .../IContextualFeatureFilter.cs | 2 +- .../IFeatureDefinitionProvider.cs | 4 ++-- src/Microsoft.FeatureManagement/IFeatureFilter.cs | 2 +- src/Microsoft.FeatureManagement/IFeatureManager.cs | 6 +++--- src/Microsoft.FeatureManagement/ISessionManager.cs | 4 ++-- .../Targeting/ITargetingContextAccessor.cs | 2 +- tests/Tests.FeatureManagement/ContextualTestFilter.cs | 2 +- .../InMemoryFeatureDefinitionProvider.cs | 4 ++-- tests/Tests.FeatureManagement/InvalidFeatureFilter.cs | 8 ++++---- .../OnDemandTargetingContextAccessor.cs | 2 +- tests/Tests.FeatureManagement/TestFilter.cs | 2 +- 17 files changed, 27 insertions(+), 27 deletions(-) diff --git a/examples/ConsoleApp/FeatureFilters/AccountIdFilter.cs b/examples/ConsoleApp/FeatureFilters/AccountIdFilter.cs index 193e87d4..3d236eed 100644 --- a/examples/ConsoleApp/FeatureFilters/AccountIdFilter.cs +++ b/examples/ConsoleApp/FeatureFilters/AccountIdFilter.cs @@ -18,7 +18,7 @@ namespace Consoto.Banking.AccountService.FeatureManagement [FilterAlias("AccountId")] class AccountIdFilter : IContextualFeatureFilter { - public Task EvaluateAsync(FeatureFilterEvaluationContext featureEvaluationContext, IAccountContext accountContext, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext featureEvaluationContext, IAccountContext accountContext, CancellationToken _) { if (string.IsNullOrEmpty(accountContext?.AccountId)) { diff --git a/examples/FeatureFlagDemo/BrowserFilter.cs b/examples/FeatureFlagDemo/BrowserFilter.cs index cd701078..e33f58c7 100644 --- a/examples/FeatureFlagDemo/BrowserFilter.cs +++ b/examples/FeatureFlagDemo/BrowserFilter.cs @@ -24,7 +24,7 @@ public BrowserFilter(IHttpContextAccessor httpContextAccessor) _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); } - public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken _) { BrowserFilterSettings settings = context.Parameters.Get() ?? new BrowserFilterSettings(); diff --git a/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs b/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs index 097605b6..931be833 100644 --- a/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs +++ b/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs @@ -24,7 +24,7 @@ public HttpContextTargetingContextAccessor(IHttpContextAccessor httpContextAcces _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); } - public ValueTask GetContextAsync(CancellationToken cancellationToken) + public ValueTask GetContextAsync(CancellationToken _) { HttpContext httpContext = _httpContextAccessor.HttpContext; diff --git a/examples/FeatureFlagDemo/SuperUserFilter.cs b/examples/FeatureFlagDemo/SuperUserFilter.cs index 48ab33ec..1e188704 100644 --- a/examples/FeatureFlagDemo/SuperUserFilter.cs +++ b/examples/FeatureFlagDemo/SuperUserFilter.cs @@ -9,7 +9,7 @@ namespace FeatureFlagDemo.FeatureManagement.FeatureFilters { public class SuperUserFilter : IFeatureFilter { - public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken _) { return Task.FromResult(false); } diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index bac5a94a..3cf36d30 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -41,7 +41,7 @@ public void Dispose() _changeSubscription = null; } - public Task GetFeatureDefinitionAsync(string featureName, CancellationToken cancellationToken) + public Task GetFeatureDefinitionAsync(string featureName, CancellationToken _) { if (featureName == null) { @@ -64,7 +64,7 @@ public Task GetFeatureDefinitionAsync(string featureName, Can // The async key word is necessary for creating IAsyncEnumerable. // The need to disable this warning occurs when implementaing async stream synchronously. #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public async IAsyncEnumerable GetAllFeatureDefinitionsAsync([EnumeratorCancellation] CancellationToken cancellationToken) + public async IAsyncEnumerable GetAllFeatureDefinitionsAsync([EnumeratorCancellation] CancellationToken _) #pragma warning restore CS1998 { if (Interlocked.Exchange(ref _stale, 0) != 0) diff --git a/src/Microsoft.FeatureManagement/EmptySessionManager.cs b/src/Microsoft.FeatureManagement/EmptySessionManager.cs index 7658a0e4..7f09b910 100644 --- a/src/Microsoft.FeatureManagement/EmptySessionManager.cs +++ b/src/Microsoft.FeatureManagement/EmptySessionManager.cs @@ -11,12 +11,12 @@ namespace Microsoft.FeatureManagement /// class EmptySessionManager : ISessionManager { - public Task SetAsync(string featureName, bool enabled, CancellationToken cancellationToken) + public Task SetAsync(string featureName, bool enabled, CancellationToken _) { return Task.CompletedTask; } - public Task GetAsync(string featureName, CancellationToken cancellationToken) + public Task GetAsync(string featureName, CancellationToken _) { return Task.FromResult((bool?)null); } diff --git a/src/Microsoft.FeatureManagement/IContextualFeatureFilter.cs b/src/Microsoft.FeatureManagement/IContextualFeatureFilter.cs index 5df783d0..84deb352 100644 --- a/src/Microsoft.FeatureManagement/IContextualFeatureFilter.cs +++ b/src/Microsoft.FeatureManagement/IContextualFeatureFilter.cs @@ -21,6 +21,6 @@ public interface IContextualFeatureFilter : IFeatureFilterMetadata /// A context defined by the application that is passed in to the feature management system to provide contextual information for evaluating a feature's state. /// The cancellation token to cancel the operation. /// True if the filter's criteria has been met, false otherwise. - Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, TContext appContext, CancellationToken cancellationToken = default); + Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, TContext appContext, CancellationToken cancellationToken); } } diff --git a/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs index c8c37494..3b5adc15 100644 --- a/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs @@ -18,13 +18,13 @@ public interface IFeatureDefinitionProvider /// The name of the feature to retrieve the definition for. /// The cancellation token to cancel the operation. /// The feature's definition. - Task GetFeatureDefinitionAsync(string featureName, CancellationToken cancellationToken = default); + Task GetFeatureDefinitionAsync(string featureName, CancellationToken cancellationToken); /// /// Retrieves definitions for all features. /// /// The cancellation token to cancel the operation. /// An enumerator which provides asynchronous iteration over feature definitions. - IAsyncEnumerable GetAllFeatureDefinitionsAsync(CancellationToken cancellationToken = default); + IAsyncEnumerable GetAllFeatureDefinitionsAsync(CancellationToken cancellationToken); } } diff --git a/src/Microsoft.FeatureManagement/IFeatureFilter.cs b/src/Microsoft.FeatureManagement/IFeatureFilter.cs index 416772db..865a40dc 100644 --- a/src/Microsoft.FeatureManagement/IFeatureFilter.cs +++ b/src/Microsoft.FeatureManagement/IFeatureFilter.cs @@ -17,6 +17,6 @@ public interface IFeatureFilter : IFeatureFilterMetadata /// A feature filter evaluation context that contains information that may be needed to evaluate the filter. This context includes configuration, if any, for this filter for the feature being evaluated. /// The cancellation token to cancel the operation. /// True if the filter's criteria has been met, false otherwise. - Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken cancellationToken = default); + Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken cancellationToken); } } diff --git a/src/Microsoft.FeatureManagement/IFeatureManager.cs b/src/Microsoft.FeatureManagement/IFeatureManager.cs index 53231f89..d9b07e6a 100644 --- a/src/Microsoft.FeatureManagement/IFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IFeatureManager.cs @@ -17,7 +17,7 @@ public interface IFeatureManager /// /// The cancellation token to cancel the operation. /// An enumerator which provides asynchronous iteration over the feature names registered in the feature manager. - IAsyncEnumerable GetFeatureNamesAsync(CancellationToken cancellationToken = default); + IAsyncEnumerable GetFeatureNamesAsync(CancellationToken cancellationToken); /// /// Checks whether a given feature is enabled. @@ -25,7 +25,7 @@ public interface IFeatureManager /// The name of the feature to check. /// The cancellation token to cancel the operation. /// True if the feature is enabled, otherwise false. - Task IsEnabledAsync(string feature, CancellationToken cancellationToken = default); + Task IsEnabledAsync(string feature, CancellationToken cancellationToken); /// /// Checks whether a given feature is enabled. @@ -34,6 +34,6 @@ public interface IFeatureManager /// A context providing information that can be used to evaluate whether a feature should be on or off. /// The cancellation token to cancel the operation. /// True if the feature is enabled, otherwise false. - Task IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken = default); + Task IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken); } } diff --git a/src/Microsoft.FeatureManagement/ISessionManager.cs b/src/Microsoft.FeatureManagement/ISessionManager.cs index f6327932..1c0656f1 100644 --- a/src/Microsoft.FeatureManagement/ISessionManager.cs +++ b/src/Microsoft.FeatureManagement/ISessionManager.cs @@ -17,7 +17,7 @@ public interface ISessionManager /// The name of the feature. /// The state of the feature. /// The cancellation token to cancel the operation. - Task SetAsync(string featureName, bool enabled, CancellationToken cancellationToken = default); + Task SetAsync(string featureName, bool enabled, CancellationToken cancellationToken); /// /// Queries the session manager for the session's feature state, if any, for the given feature. @@ -25,6 +25,6 @@ public interface ISessionManager /// The name of the feature. /// The cancellation token to cancel the operation. /// The state of the feature if it is present in the session, otherwise null. - Task GetAsync(string featureName, CancellationToken cancellationToken = default); + Task GetAsync(string featureName, CancellationToken cancellationToken); } } diff --git a/src/Microsoft.FeatureManagement/Targeting/ITargetingContextAccessor.cs b/src/Microsoft.FeatureManagement/Targeting/ITargetingContextAccessor.cs index 04e5f709..2739e628 100644 --- a/src/Microsoft.FeatureManagement/Targeting/ITargetingContextAccessor.cs +++ b/src/Microsoft.FeatureManagement/Targeting/ITargetingContextAccessor.cs @@ -16,6 +16,6 @@ public interface ITargetingContextAccessor /// /// The cancellation token to cancel the operation. /// The current targeting context. - ValueTask GetContextAsync(CancellationToken cancellationToken = default); + ValueTask GetContextAsync(CancellationToken cancellationToken); } } diff --git a/tests/Tests.FeatureManagement/ContextualTestFilter.cs b/tests/Tests.FeatureManagement/ContextualTestFilter.cs index 87241891..91bd6222 100644 --- a/tests/Tests.FeatureManagement/ContextualTestFilter.cs +++ b/tests/Tests.FeatureManagement/ContextualTestFilter.cs @@ -12,7 +12,7 @@ class ContextualTestFilter : IContextualFeatureFilter { public Func ContextualCallback { get; set; } - public Task EvaluateAsync(FeatureFilterEvaluationContext context, IAccountContext accountContext, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, IAccountContext accountContext, CancellationToken _) { return Task.FromResult(ContextualCallback?.Invoke(context, accountContext) ?? false); } diff --git a/tests/Tests.FeatureManagement/InMemoryFeatureDefinitionProvider.cs b/tests/Tests.FeatureManagement/InMemoryFeatureDefinitionProvider.cs index b91dcad7..3aad4c8c 100644 --- a/tests/Tests.FeatureManagement/InMemoryFeatureDefinitionProvider.cs +++ b/tests/Tests.FeatureManagement/InMemoryFeatureDefinitionProvider.cs @@ -18,7 +18,7 @@ public InMemoryFeatureDefinitionProvider(IEnumerable featureD } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public async IAsyncEnumerable GetAllFeatureDefinitionsAsync([EnumeratorCancellation] CancellationToken cancellationToken) + public async IAsyncEnumerable GetAllFeatureDefinitionsAsync([EnumeratorCancellation] CancellationToken _) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { foreach (FeatureDefinition definition in _definitions) @@ -27,7 +27,7 @@ public async IAsyncEnumerable GetAllFeatureDefinitionsAsync([ } } - public Task GetFeatureDefinitionAsync(string featureName, CancellationToken cancellationToken) + public Task GetFeatureDefinitionAsync(string featureName, CancellationToken _) { return Task.FromResult(_definitions.FirstOrDefault(definitions => definitions.Name.Equals(featureName, StringComparison.OrdinalIgnoreCase))); } diff --git a/tests/Tests.FeatureManagement/InvalidFeatureFilter.cs b/tests/Tests.FeatureManagement/InvalidFeatureFilter.cs index cc42aaef..4f81e3e2 100644 --- a/tests/Tests.FeatureManagement/InvalidFeatureFilter.cs +++ b/tests/Tests.FeatureManagement/InvalidFeatureFilter.cs @@ -11,12 +11,12 @@ namespace Tests.FeatureManagement // Cannot implement more than one IFeatureFilter interface class InvalidFeatureFilter : IContextualFeatureFilter, IContextualFeatureFilter { - public Task EvaluateAsync(FeatureFilterEvaluationContext context, IAccountContext accountContext, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, IAccountContext accountContext, CancellationToken _) { return Task.FromResult(false); } - public Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, object appContext, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, object appContext, CancellationToken _) { return Task.FromResult(false); } @@ -26,12 +26,12 @@ public Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterCont // Cannot implement more than one IFeatureFilter interface class InvalidFeatureFilter2 : IFeatureFilter, IContextualFeatureFilter { - public Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, object appContext, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, object appContext, CancellationToken _) { return Task.FromResult(false); } - public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken _) { return Task.FromResult(false); } diff --git a/tests/Tests.FeatureManagement/OnDemandTargetingContextAccessor.cs b/tests/Tests.FeatureManagement/OnDemandTargetingContextAccessor.cs index 5ba376fb..aaff4a41 100644 --- a/tests/Tests.FeatureManagement/OnDemandTargetingContextAccessor.cs +++ b/tests/Tests.FeatureManagement/OnDemandTargetingContextAccessor.cs @@ -11,7 +11,7 @@ class OnDemandTargetingContextAccessor : ITargetingContextAccessor { public TargetingContext Current { get; set; } - public ValueTask GetContextAsync(CancellationToken cancellationToken) + public ValueTask GetContextAsync(CancellationToken _) { return new ValueTask(Current); } diff --git a/tests/Tests.FeatureManagement/TestFilter.cs b/tests/Tests.FeatureManagement/TestFilter.cs index 5ec056d0..be6e3be7 100644 --- a/tests/Tests.FeatureManagement/TestFilter.cs +++ b/tests/Tests.FeatureManagement/TestFilter.cs @@ -12,7 +12,7 @@ class TestFilter : IFeatureFilter { public Func Callback { get; set; } - public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken _) { return Task.FromResult(Callback?.Invoke(context) ?? false); } From e53186327fde43423ab91374a107800a17113509 Mon Sep 17 00:00:00 2001 From: Jimmy Campbell Date: Tue, 31 Aug 2021 14:45:35 -0400 Subject: [PATCH 02/10] Revert "Add cancellation token parameter to async feature management interfaces. (#131)" (#139) This reverts commit 7e2a15d074be1e68b3ff111e0c21bdc9dfa7e19a. --- .../FeatureFilters/AccountIdFilter.cs | 3 +- examples/ConsoleApp/Program.cs | 3 +- examples/FeatureFlagDemo/BrowserFilter.cs | 3 +- .../Controllers/HomeController.cs | 5 +-- .../HttpContextTargetingContextAccessor.cs | 3 +- examples/FeatureFlagDemo/SuperUserFilter.cs | 3 +- examples/TargetingConsoleApp/Program.cs | 3 +- .../FeatureGateAttribute.cs | 4 +- .../FeatureGatedAsyncActionFilter.cs | 2 +- .../TagHelpers/FeatureTagHelper.cs | 5 +-- .../UseForFeatureExtensions.cs | 2 +- .../ConfigurationFeatureDefinitionProvider.cs | 5 +-- .../ContextualFeatureFilterEvaluator.cs | 30 +++++-------- .../EmptySessionManager.cs | 5 +-- .../FeatureFilters/PercentageFilter.cs | 4 +- .../FeatureFilters/TimeWindowFilter.cs | 4 +- .../FeatureManager.cs | 34 +++++--------- .../FeatureManagerSnapshot.cs | 14 +++--- .../IContextualFeatureFilter.cs | 4 +- .../IFeatureDefinitionProvider.cs | 7 +-- .../IFeatureFilter.cs | 4 +- .../IFeatureManager.cs | 10 ++--- .../ISessionManager.cs | 7 +-- .../Microsoft.FeatureManagement.csproj | 5 +-- .../Targeting/ContextualTargetingFilter.cs | 4 +- .../Targeting/ITargetingContextAccessor.cs | 4 +- .../Targeting/TargetingFilter.cs | 8 ++-- .../ContextualTestFilter.cs | 3 +- .../FeatureManagement.cs | 44 +++++++++---------- .../InMemoryFeatureDefinitionProvider.cs | 6 +-- .../InvalidFeatureFilter.cs | 9 ++-- .../OnDemandTargetingContextAccessor.cs | 3 +- tests/Tests.FeatureManagement/TestFilter.cs | 3 +- 33 files changed, 95 insertions(+), 158 deletions(-) diff --git a/examples/ConsoleApp/FeatureFilters/AccountIdFilter.cs b/examples/ConsoleApp/FeatureFilters/AccountIdFilter.cs index 3d236eed..1fd56d4a 100644 --- a/examples/ConsoleApp/FeatureFilters/AccountIdFilter.cs +++ b/examples/ConsoleApp/FeatureFilters/AccountIdFilter.cs @@ -6,7 +6,6 @@ using Microsoft.FeatureManagement; using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; namespace Consoto.Banking.AccountService.FeatureManagement @@ -18,7 +17,7 @@ namespace Consoto.Banking.AccountService.FeatureManagement [FilterAlias("AccountId")] class AccountIdFilter : IContextualFeatureFilter { - public Task EvaluateAsync(FeatureFilterEvaluationContext featureEvaluationContext, IAccountContext accountContext, CancellationToken _) + public Task EvaluateAsync(FeatureFilterEvaluationContext featureEvaluationContext, IAccountContext accountContext) { if (string.IsNullOrEmpty(accountContext?.AccountId)) { diff --git a/examples/ConsoleApp/Program.cs b/examples/ConsoleApp/Program.cs index afe4c52f..e0ab1021 100644 --- a/examples/ConsoleApp/Program.cs +++ b/examples/ConsoleApp/Program.cs @@ -8,7 +8,6 @@ using Microsoft.FeatureManagement.FeatureFilters; using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; namespace Consoto.Banking.AccountService @@ -59,7 +58,7 @@ public static async Task Main(string[] args) AccountId = account }; - bool enabled = await featureManager.IsEnabledAsync(FeatureName, accountServiceContext, CancellationToken.None); + bool enabled = await featureManager.IsEnabledAsync(FeatureName, accountServiceContext); // // Output results diff --git a/examples/FeatureFlagDemo/BrowserFilter.cs b/examples/FeatureFlagDemo/BrowserFilter.cs index e33f58c7..efeb8e70 100644 --- a/examples/FeatureFlagDemo/BrowserFilter.cs +++ b/examples/FeatureFlagDemo/BrowserFilter.cs @@ -6,7 +6,6 @@ using Microsoft.FeatureManagement; using System; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace FeatureFlagDemo.FeatureManagement.FeatureFilters @@ -24,7 +23,7 @@ public BrowserFilter(IHttpContextAccessor httpContextAccessor) _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); } - public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken _) + public Task EvaluateAsync(FeatureFilterEvaluationContext context) { BrowserFilterSettings settings = context.Parameters.Get() ?? new BrowserFilterSettings(); diff --git a/examples/FeatureFlagDemo/Controllers/HomeController.cs b/examples/FeatureFlagDemo/Controllers/HomeController.cs index a939a88e..c4363967 100644 --- a/examples/FeatureFlagDemo/Controllers/HomeController.cs +++ b/examples/FeatureFlagDemo/Controllers/HomeController.cs @@ -8,7 +8,6 @@ using Microsoft.FeatureManagement; using Microsoft.FeatureManagement.Mvc; using System.Threading.Tasks; -using System.Threading; namespace FeatureFlagDemo.Controllers { @@ -27,11 +26,11 @@ public IActionResult Index() return View(); } - public async Task About(CancellationToken cancellationToken) + public async Task About() { ViewData["Message"] = "Your application description page."; - if (await _featureManager.IsEnabledAsync(nameof(MyFeatureFlags.CustomViewData), cancellationToken)) + if (await _featureManager.IsEnabledAsync(nameof(MyFeatureFlags.CustomViewData))) { ViewData["Message"] = $"This is FANCY CONTENT you can see only if '{nameof(MyFeatureFlags.CustomViewData)}' is enabled."; }; diff --git a/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs b/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs index 931be833..9f9c8964 100644 --- a/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs +++ b/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Security.Claims; -using System.Threading; using System.Threading.Tasks; namespace FeatureFlagDemo @@ -24,7 +23,7 @@ public HttpContextTargetingContextAccessor(IHttpContextAccessor httpContextAcces _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); } - public ValueTask GetContextAsync(CancellationToken _) + public ValueTask GetContextAsync() { HttpContext httpContext = _httpContextAccessor.HttpContext; diff --git a/examples/FeatureFlagDemo/SuperUserFilter.cs b/examples/FeatureFlagDemo/SuperUserFilter.cs index 1e188704..25dc8e5f 100644 --- a/examples/FeatureFlagDemo/SuperUserFilter.cs +++ b/examples/FeatureFlagDemo/SuperUserFilter.cs @@ -2,14 +2,13 @@ // Licensed under the MIT license. // using Microsoft.FeatureManagement; -using System.Threading; using System.Threading.Tasks; namespace FeatureFlagDemo.FeatureManagement.FeatureFilters { public class SuperUserFilter : IFeatureFilter { - public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken _) + public Task EvaluateAsync(FeatureFilterEvaluationContext context) { return Task.FromResult(false); } diff --git a/examples/TargetingConsoleApp/Program.cs b/examples/TargetingConsoleApp/Program.cs index 10e64223..953f609e 100644 --- a/examples/TargetingConsoleApp/Program.cs +++ b/examples/TargetingConsoleApp/Program.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace Consoto.Banking.HelpDesk @@ -63,7 +62,7 @@ public static async Task Main(string[] args) Groups = user.Groups }; - bool enabled = await featureManager.IsEnabledAsync(FeatureName, targetingContext, CancellationToken.None); + bool enabled = await featureManager.IsEnabledAsync(FeatureName, targetingContext); // // Output results diff --git a/src/Microsoft.FeatureManagement.AspNetCore/FeatureGateAttribute.cs b/src/Microsoft.FeatureManagement.AspNetCore/FeatureGateAttribute.cs index 427df019..bd88800a 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/FeatureGateAttribute.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/FeatureGateAttribute.cs @@ -106,8 +106,8 @@ public override async Task OnActionExecutionAsync(ActionExecutingContext context // // Enabled state is determined by either 'any' or 'all' features being enabled. bool enabled = RequirementType == RequirementType.All ? - await Features.All(async feature => await fm.IsEnabledAsync(feature, context.HttpContext.RequestAborted).ConfigureAwait(false)).ConfigureAwait(false) : - await Features.Any(async feature => await fm.IsEnabledAsync(feature, context.HttpContext.RequestAborted).ConfigureAwait(false)).ConfigureAwait(false); + await Features.All(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false)) : + await Features.Any(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false)); if (enabled) { diff --git a/src/Microsoft.FeatureManagement.AspNetCore/FeatureGatedAsyncActionFilter.cs b/src/Microsoft.FeatureManagement.AspNetCore/FeatureGatedAsyncActionFilter.cs index 80a68a7e..594620ae 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/FeatureGatedAsyncActionFilter.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/FeatureGatedAsyncActionFilter.cs @@ -30,7 +30,7 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE { IFeatureManager featureManager = context.HttpContext.RequestServices.GetRequiredService(); - if (await featureManager.IsEnabledAsync(FeatureName, context.HttpContext.RequestAborted).ConfigureAwait(false)) + if (await featureManager.IsEnabledAsync(FeatureName).ConfigureAwait(false)) { IServiceProvider serviceProvider = context.HttpContext.RequestServices.GetRequiredService(); diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs index 651c0068..4a0da1e0 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Razor.TagHelpers; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement.Mvc.TagHelpers @@ -56,8 +55,8 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu IEnumerable names = Name.Split(',').Select(n => n.Trim()); enabled = Requirement == RequirementType.All ? - await names.All(async n => await _featureManager.IsEnabledAsync(n, CancellationToken.None).ConfigureAwait(false)).ConfigureAwait(false) : - await names.Any(async n => await _featureManager.IsEnabledAsync(n, CancellationToken.None).ConfigureAwait(false)).ConfigureAwait(false); + await names.All(async n => await _featureManager.IsEnabledAsync(n).ConfigureAwait(false)) : + await names.Any(async n => await _featureManager.IsEnabledAsync(n).ConfigureAwait(false)); } if (Negate) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/UseForFeatureExtensions.cs b/src/Microsoft.FeatureManagement.AspNetCore/UseForFeatureExtensions.cs index c8562b7a..4774ee9b 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/UseForFeatureExtensions.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/UseForFeatureExtensions.cs @@ -57,7 +57,7 @@ public static IApplicationBuilder UseForFeature(this IApplicationBuilder app, st { IFeatureManager fm = context.RequestServices.GetRequiredService(); - if (await fm.IsEnabledAsync(featureName, context.RequestAborted).ConfigureAwait(false)) + if (await fm.IsEnabledAsync(featureName).ConfigureAwait(false)) { await branch(context).ConfigureAwait(false); } diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index 3cf36d30..8763b850 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -7,7 +7,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -41,7 +40,7 @@ public void Dispose() _changeSubscription = null; } - public Task GetFeatureDefinitionAsync(string featureName, CancellationToken _) + public Task GetFeatureDefinitionAsync(string featureName) { if (featureName == null) { @@ -64,7 +63,7 @@ public Task GetFeatureDefinitionAsync(string featureName, Can // The async key word is necessary for creating IAsyncEnumerable. // The need to disable this warning occurs when implementaing async stream synchronously. #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public async IAsyncEnumerable GetAllFeatureDefinitionsAsync([EnumeratorCancellation] CancellationToken _) + public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() #pragma warning restore CS1998 { if (Interlocked.Exchange(ref _stale, 0) != 0) diff --git a/src/Microsoft.FeatureManagement/ContextualFeatureFilterEvaluator.cs b/src/Microsoft.FeatureManagement/ContextualFeatureFilterEvaluator.cs index 53df9f9c..baf9220a 100644 --- a/src/Microsoft.FeatureManagement/ContextualFeatureFilterEvaluator.cs +++ b/src/Microsoft.FeatureManagement/ContextualFeatureFilterEvaluator.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement @@ -16,7 +15,7 @@ namespace Microsoft.FeatureManagement class ContextualFeatureFilterEvaluator : IContextualFeatureFilter { private IFeatureFilterMetadata _filter; - private Func> _evaluateFunc; + private Func> _evaluateFunc; public ContextualFeatureFilterEvaluator(IFeatureFilterMetadata filter, Type appContextType) { @@ -44,14 +43,14 @@ public ContextualFeatureFilterEvaluator(IFeatureFilterMetadata filter, Type appC _filter = filter; } - public Task EvaluateAsync(FeatureFilterEvaluationContext evaluationContext, object context, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext evaluationContext, object context) { if (_evaluateFunc == null) { return Task.FromResult(false); } - return _evaluateFunc(_filter, evaluationContext, context, cancellationToken); + return _evaluateFunc(_filter, evaluationContext, context); } public static bool IsContextualFilter(IFeatureFilterMetadata filter, Type appContextType) @@ -73,7 +72,7 @@ private static Type GetContextualFilterInterface(IFeatureFilterMetadata filter, return targetInterface; } - private static Func> TypeAgnosticEvaluate(Type filterType, MethodInfo method) + private static Func> TypeAgnosticEvaluate(Type filterType, MethodInfo method) { // // Get the generic version of the evaluation helper method @@ -83,30 +82,23 @@ private static Func>)typeAgnosticDelegate; + return (Func>)typeAgnosticDelegate; } - private static Func> GenericTypeAgnosticEvaluate(MethodInfo method) + private static Func> GenericTypeAgnosticEvaluate(MethodInfo method) { - Func> func = - (Func>) - Delegate.CreateDelegate(typeof(Func>), method); + Func> func = (Func>)Delegate.CreateDelegate + (typeof(Func>), method); - Func> genericDelegate = - (object target, FeatureFilterEvaluationContext param1, object param2, CancellationToken param3) => - func((TTarget)target, param1, (TParam2)param2, param3); + Func> genericDelegate = (object target, FeatureFilterEvaluationContext param1, object param2) => func((TTarget)target, param1, (TParam2)param2); return genericDelegate; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.FeatureManagement/EmptySessionManager.cs b/src/Microsoft.FeatureManagement/EmptySessionManager.cs index 7f09b910..fd4b3d3f 100644 --- a/src/Microsoft.FeatureManagement/EmptySessionManager.cs +++ b/src/Microsoft.FeatureManagement/EmptySessionManager.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement @@ -11,12 +10,12 @@ namespace Microsoft.FeatureManagement /// class EmptySessionManager : ISessionManager { - public Task SetAsync(string featureName, bool enabled, CancellationToken _) + public Task SetAsync(string featureName, bool enabled) { return Task.CompletedTask; } - public Task GetAsync(string featureName, CancellationToken _) + public Task GetAsync(string featureName) { return Task.FromResult((bool?)null); } diff --git a/src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs b/src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs index c63441cc..e642eb35 100644 --- a/src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs +++ b/src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.FeatureManagement.Utils; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement.FeatureFilters @@ -31,9 +30,8 @@ public PercentageFilter(ILoggerFactory loggerFactory) /// Performs a percentage based evaluation to determine whether a feature is enabled. /// /// The feature evaluation context. - /// The cancellation token to cancel the operation. /// True if the feature is enabled, false otherwise. - public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext context) { PercentageFilterSettings settings = context.Parameters.Get() ?? new PercentageFilterSettings(); diff --git a/src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs b/src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs index 4c3abe48..97c21b07 100644 --- a/src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs +++ b/src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement.FeatureFilters @@ -31,9 +30,8 @@ public TimeWindowFilter(ILoggerFactory loggerFactory) /// Evaluates whether a feature is enabled based on a configurable time window. /// /// The feature evaluation context. - /// The cancellation token to cancel the operation. /// True if the feature is enabled, false otherwise. - public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext context) { TimeWindowFilterSettings settings = context.Parameters.Get() ?? new TimeWindowFilterSettings(); diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index c57f03c5..25873f50 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -7,8 +7,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement @@ -42,31 +40,29 @@ public FeatureManager( _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); } - public Task IsEnabledAsync(string feature, CancellationToken cancellationToken) + public Task IsEnabledAsync(string feature) { - return IsEnabledAsync(feature, null, false, cancellationToken); + return IsEnabledAsync(feature, null, false); } - public Task IsEnabledAsync(string feature, TContext appContext, CancellationToken cancellationToken) + public Task IsEnabledAsync(string feature, TContext appContext) { - return IsEnabledAsync(feature, appContext, true, cancellationToken); + return IsEnabledAsync(feature, appContext, true); } - public async IAsyncEnumerable GetFeatureNamesAsync([EnumeratorCancellation] CancellationToken cancellationToken) + public async IAsyncEnumerable GetFeatureNamesAsync() { - await foreach (FeatureDefinition featureDefintion in _featureDefinitionProvider - .GetAllFeatureDefinitionsAsync(cancellationToken) - .ConfigureAwait(false)) + await foreach (FeatureDefinition featureDefintion in _featureDefinitionProvider.GetAllFeatureDefinitionsAsync().ConfigureAwait(false)) { yield return featureDefintion.Name; } } - private async Task IsEnabledAsync(string feature, TContext appContext, bool useAppContext, CancellationToken cancellationToken) + private async Task IsEnabledAsync(string feature, TContext appContext, bool useAppContext) { foreach (ISessionManager sessionManager in _sessionManagers) { - bool? readSessionResult = await sessionManager.GetAsync(feature, cancellationToken).ConfigureAwait(false); + bool? readSessionResult = await sessionManager.GetAsync(feature).ConfigureAwait(false); if (readSessionResult.HasValue) { @@ -76,9 +72,7 @@ private async Task IsEnabledAsync(string feature, TContext appCo bool enabled = false; - FeatureDefinition featureDefinition = await _featureDefinitionProvider - .GetFeatureDefinitionAsync(feature, cancellationToken) - .ConfigureAwait(false); + FeatureDefinition featureDefinition = await _featureDefinitionProvider.GetFeatureDefinitionAsync(feature).ConfigureAwait(false); if (featureDefinition != null) { @@ -128,9 +122,7 @@ private async Task IsEnabledAsync(string feature, TContext appCo { ContextualFeatureFilterEvaluator contextualFilter = GetContextualFeatureFilter(featureFilterConfiguration.Name, typeof(TContext)); - if (contextualFilter != null && await contextualFilter - .EvaluateAsync(context, appContext, cancellationToken) - .ConfigureAwait(false)) + if (contextualFilter != null && await contextualFilter.EvaluateAsync(context, appContext).ConfigureAwait(false)) { enabled = true; @@ -140,9 +132,7 @@ private async Task IsEnabledAsync(string feature, TContext appCo // // IFeatureFilter - if (filter is IFeatureFilter featureFilter && await featureFilter - .EvaluateAsync(context, cancellationToken) - .ConfigureAwait(false)) + if (filter is IFeatureFilter featureFilter && await featureFilter.EvaluateAsync(context).ConfigureAwait(false)) { enabled = true; @@ -154,7 +144,7 @@ private async Task IsEnabledAsync(string feature, TContext appCo foreach (ISessionManager sessionManager in _sessionManagers) { - await sessionManager.SetAsync(feature, enabled, cancellationToken).ConfigureAwait(false); + await sessionManager.SetAsync(feature, enabled).ConfigureAwait(false); } return enabled; diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index 46549f4d..a218e1cc 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -3,8 +3,6 @@ // using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement @@ -23,13 +21,13 @@ public FeatureManagerSnapshot(IFeatureManager featureManager) _featureManager = featureManager ?? throw new ArgumentNullException(nameof(featureManager)); } - public async IAsyncEnumerable GetFeatureNamesAsync([EnumeratorCancellation]CancellationToken cancellationToken) + public async IAsyncEnumerable GetFeatureNamesAsync() { if (_featureNames == null) { var featureNames = new List(); - await foreach (string featureName in _featureManager.GetFeatureNamesAsync(cancellationToken).ConfigureAwait(false)) + await foreach (string featureName in _featureManager.GetFeatureNamesAsync().ConfigureAwait(false)) { featureNames.Add(featureName); } @@ -43,7 +41,7 @@ public async IAsyncEnumerable GetFeatureNamesAsync([EnumeratorCancellati } } - public async Task IsEnabledAsync(string feature, CancellationToken cancellationToken) + public async Task IsEnabledAsync(string feature) { // // First, check local cache @@ -52,14 +50,14 @@ public async Task IsEnabledAsync(string feature, CancellationToken cancell return _flagCache[feature]; } - bool enabled = await _featureManager.IsEnabledAsync(feature, cancellationToken).ConfigureAwait(false); + bool enabled = await _featureManager.IsEnabledAsync(feature).ConfigureAwait(false); _flagCache[feature] = enabled; return enabled; } - public async Task IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken) + public async Task IsEnabledAsync(string feature, TContext context) { // // First, check local cache @@ -68,7 +66,7 @@ public async Task IsEnabledAsync(string feature, TContext contex return _flagCache[feature]; } - bool enabled = await _featureManager.IsEnabledAsync(feature, context, cancellationToken).ConfigureAwait(false); + bool enabled = await _featureManager.IsEnabledAsync(feature, context).ConfigureAwait(false); _flagCache[feature] = enabled; diff --git a/src/Microsoft.FeatureManagement/IContextualFeatureFilter.cs b/src/Microsoft.FeatureManagement/IContextualFeatureFilter.cs index 84deb352..64586334 100644 --- a/src/Microsoft.FeatureManagement/IContextualFeatureFilter.cs +++ b/src/Microsoft.FeatureManagement/IContextualFeatureFilter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement @@ -19,8 +18,7 @@ public interface IContextualFeatureFilter : IFeatureFilterMetadata /// /// A feature filter evaluation context that contains information that may be needed to evaluate the filter. This context includes configuration, if any, for this filter for the feature being evaluated. /// A context defined by the application that is passed in to the feature management system to provide contextual information for evaluating a feature's state. - /// The cancellation token to cancel the operation. /// True if the filter's criteria has been met, false otherwise. - Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, TContext appContext, CancellationToken cancellationToken); + Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, TContext appContext); } } diff --git a/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs index 3b5adc15..bc4895b9 100644 --- a/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. // using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement @@ -16,15 +15,13 @@ public interface IFeatureDefinitionProvider /// Retrieves the definition for a given feature. /// /// The name of the feature to retrieve the definition for. - /// The cancellation token to cancel the operation. /// The feature's definition. - Task GetFeatureDefinitionAsync(string featureName, CancellationToken cancellationToken); + Task GetFeatureDefinitionAsync(string featureName); /// /// Retrieves definitions for all features. /// - /// The cancellation token to cancel the operation. /// An enumerator which provides asynchronous iteration over feature definitions. - IAsyncEnumerable GetAllFeatureDefinitionsAsync(CancellationToken cancellationToken); + IAsyncEnumerable GetAllFeatureDefinitionsAsync(); } } diff --git a/src/Microsoft.FeatureManagement/IFeatureFilter.cs b/src/Microsoft.FeatureManagement/IFeatureFilter.cs index 865a40dc..e6d914df 100644 --- a/src/Microsoft.FeatureManagement/IFeatureFilter.cs +++ b/src/Microsoft.FeatureManagement/IFeatureFilter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement @@ -15,8 +14,7 @@ public interface IFeatureFilter : IFeatureFilterMetadata /// Evaluates the feature filter to see if the filter's criteria for being enabled has been satisfied. /// /// A feature filter evaluation context that contains information that may be needed to evaluate the filter. This context includes configuration, if any, for this filter for the feature being evaluated. - /// The cancellation token to cancel the operation. /// True if the filter's criteria has been met, false otherwise. - Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken cancellationToken); + Task EvaluateAsync(FeatureFilterEvaluationContext context); } } diff --git a/src/Microsoft.FeatureManagement/IFeatureManager.cs b/src/Microsoft.FeatureManagement/IFeatureManager.cs index d9b07e6a..1b4ea0cf 100644 --- a/src/Microsoft.FeatureManagement/IFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IFeatureManager.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. // using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement @@ -15,25 +14,22 @@ public interface IFeatureManager /// /// Retrieves a list of feature names registered in the feature manager. /// - /// The cancellation token to cancel the operation. /// An enumerator which provides asynchronous iteration over the feature names registered in the feature manager. - IAsyncEnumerable GetFeatureNamesAsync(CancellationToken cancellationToken); + IAsyncEnumerable GetFeatureNamesAsync(); /// /// Checks whether a given feature is enabled. /// /// The name of the feature to check. - /// The cancellation token to cancel the operation. /// True if the feature is enabled, otherwise false. - Task IsEnabledAsync(string feature, CancellationToken cancellationToken); + Task IsEnabledAsync(string feature); /// /// Checks whether a given feature is enabled. /// /// The name of the feature to check. /// A context providing information that can be used to evaluate whether a feature should be on or off. - /// The cancellation token to cancel the operation. /// True if the feature is enabled, otherwise false. - Task IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken); + Task IsEnabledAsync(string feature, TContext context); } } diff --git a/src/Microsoft.FeatureManagement/ISessionManager.cs b/src/Microsoft.FeatureManagement/ISessionManager.cs index 1c0656f1..67189375 100644 --- a/src/Microsoft.FeatureManagement/ISessionManager.cs +++ b/src/Microsoft.FeatureManagement/ISessionManager.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement @@ -16,15 +15,13 @@ public interface ISessionManager /// /// The name of the feature. /// The state of the feature. - /// The cancellation token to cancel the operation. - Task SetAsync(string featureName, bool enabled, CancellationToken cancellationToken); + Task SetAsync(string featureName, bool enabled); /// /// Queries the session manager for the session's feature state, if any, for the given feature. /// /// The name of the feature. - /// The cancellation token to cancel the operation. /// The state of the feature if it is present in the session, otherwise null. - Task GetAsync(string featureName, CancellationToken cancellationToken); + Task GetAsync(string featureName); } } diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj index 878778f0..3bb53e2d 100644 --- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj +++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj @@ -3,10 +3,9 @@ - 3 - 0 + 2 + 3 0 - -preview diff --git a/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs b/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs index a43c1a80..a2d4404e 100644 --- a/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs +++ b/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Security.Cryptography; using System.Text; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement.FeatureFilters @@ -41,10 +40,9 @@ public ContextualTargetingFilter(IOptions options, I /// /// The feature evaluation context. /// The targeting context to use during targeting evaluation. - /// The cancellation token to cancel the operation. /// Thrown if either or is null. /// True if the feature is enabled, false otherwise. - public Task EvaluateAsync(FeatureFilterEvaluationContext context, ITargetingContext targetingContext, CancellationToken cancellationToken) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, ITargetingContext targetingContext) { if (context == null) { diff --git a/src/Microsoft.FeatureManagement/Targeting/ITargetingContextAccessor.cs b/src/Microsoft.FeatureManagement/Targeting/ITargetingContextAccessor.cs index 2739e628..94c1eebd 100644 --- a/src/Microsoft.FeatureManagement/Targeting/ITargetingContextAccessor.cs +++ b/src/Microsoft.FeatureManagement/Targeting/ITargetingContextAccessor.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement.FeatureFilters @@ -14,8 +13,7 @@ public interface ITargetingContextAccessor /// /// Retrieves the current targeting context. /// - /// The cancellation token to cancel the operation. /// The current targeting context. - ValueTask GetContextAsync(CancellationToken cancellationToken); + ValueTask GetContextAsync(); } } diff --git a/src/Microsoft.FeatureManagement/Targeting/TargetingFilter.cs b/src/Microsoft.FeatureManagement/Targeting/TargetingFilter.cs index 93e753ef..283a6260 100644 --- a/src/Microsoft.FeatureManagement/Targeting/TargetingFilter.cs +++ b/src/Microsoft.FeatureManagement/Targeting/TargetingFilter.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement.FeatureFilters @@ -37,10 +36,9 @@ public TargetingFilter(IOptions options, ITargetingC /// Performs a targeting evaluation using the current to determine if a feature should be enabled. /// /// The feature evaluation context. - /// The cancellation token to cancel the operation. /// Thrown if is null. /// True if the feature is enabled, false otherwise. - public async Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken cancellationToken) + public async Task EvaluateAsync(FeatureFilterEvaluationContext context) { if (context == null) { @@ -49,7 +47,7 @@ public async Task EvaluateAsync(FeatureFilterEvaluationContext context, Ca // // Acquire targeting context via accessor - TargetingContext targetingContext = await _contextAccessor.GetContextAsync(cancellationToken).ConfigureAwait(false); + TargetingContext targetingContext = await _contextAccessor.GetContextAsync().ConfigureAwait(false); // // Ensure targeting can be performed @@ -62,7 +60,7 @@ public async Task EvaluateAsync(FeatureFilterEvaluationContext context, Ca // // Utilize contextual filter for targeting evaluation - return await _contextualFilter.EvaluateAsync(context, targetingContext, cancellationToken).ConfigureAwait(false); + return await _contextualFilter.EvaluateAsync(context, targetingContext).ConfigureAwait(false); } } } diff --git a/tests/Tests.FeatureManagement/ContextualTestFilter.cs b/tests/Tests.FeatureManagement/ContextualTestFilter.cs index 91bd6222..4bad3010 100644 --- a/tests/Tests.FeatureManagement/ContextualTestFilter.cs +++ b/tests/Tests.FeatureManagement/ContextualTestFilter.cs @@ -3,7 +3,6 @@ // using Microsoft.FeatureManagement; using System; -using System.Threading; using System.Threading.Tasks; namespace Tests.FeatureManagement @@ -12,7 +11,7 @@ class ContextualTestFilter : IContextualFeatureFilter { public Func ContextualCallback { get; set; } - public Task EvaluateAsync(FeatureFilterEvaluationContext context, IAccountContext accountContext, CancellationToken _) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, IAccountContext accountContext) { return Task.FromResult(ContextualCallback?.Invoke(context, accountContext) ?? false); } diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 0a123ec2..e30888e4 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -15,7 +15,6 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Threading; using System.Threading.Tasks; using Xunit; @@ -44,9 +43,9 @@ public async Task ReadsConfiguration() IFeatureManager featureManager = serviceProvider.GetRequiredService(); - Assert.True(await featureManager.IsEnabledAsync(OnFeature, CancellationToken.None)); + Assert.True(await featureManager.IsEnabledAsync(OnFeature)); - Assert.False(await featureManager.IsEnabledAsync(OffFeature, CancellationToken.None)); + Assert.False(await featureManager.IsEnabledAsync(OffFeature)); IEnumerable featureFilters = serviceProvider.GetRequiredService>(); @@ -67,7 +66,7 @@ public async Task ReadsConfiguration() return true; }; - await featureManager.IsEnabledAsync(ConditionalFeature, CancellationToken.None); + await featureManager.IsEnabledAsync(ConditionalFeature); Assert.True(called); } @@ -205,10 +204,10 @@ public async Task TimeWindow() IFeatureManager featureManager = provider.GetRequiredService(); - Assert.True(await featureManager.IsEnabledAsync(feature1, CancellationToken.None)); - Assert.False(await featureManager.IsEnabledAsync(feature2, CancellationToken.None)); - Assert.True(await featureManager.IsEnabledAsync(feature3, CancellationToken.None)); - Assert.False(await featureManager.IsEnabledAsync(feature4, CancellationToken.None)); + Assert.True(await featureManager.IsEnabledAsync(feature1)); + Assert.False(await featureManager.IsEnabledAsync(feature2)); + Assert.True(await featureManager.IsEnabledAsync(feature3)); + Assert.False(await featureManager.IsEnabledAsync(feature4)); } [Fact] @@ -235,7 +234,7 @@ public async Task Percentage() for (int i = 0; i < 10; i++) { - if (await featureManager.IsEnabledAsync(feature1, CancellationToken.None)) + if (await featureManager.IsEnabledAsync(feature1)) { enabledCount++; } @@ -273,21 +272,21 @@ public async Task Targeting() Assert.True(await featureManager.IsEnabledAsync(targetingTestFeature, new TargetingContext { UserId = "Jeff" - }, CancellationToken.None)); + })); // // Not targeted by user id, but targeted by default rollout Assert.True(await featureManager.IsEnabledAsync(targetingTestFeature, new TargetingContext { UserId = "Anne" - }, CancellationToken.None)); + })); // // Not targeted by user id or default rollout Assert.False(await featureManager.IsEnabledAsync(targetingTestFeature, new TargetingContext { UserId = "Patty" - }, CancellationToken.None)); + })); // // Targeted by group rollout @@ -295,7 +294,7 @@ public async Task Targeting() { UserId = "Patty", Groups = new List() { "Ring1" } - }, CancellationToken.None)); + })); // // Not targeted by user id, default rollout or group rollout @@ -303,7 +302,7 @@ public async Task Targeting() { UserId = "Isaac", Groups = new List() { "Ring1" } - }, CancellationToken.None)); + })); } [Fact] @@ -341,7 +340,7 @@ public async Task TargetingAccessor() UserId = "Jeff" }; - Assert.True(await featureManager.IsEnabledAsync(beta, CancellationToken.None)); + Assert.True(await featureManager.IsEnabledAsync(beta)); // // Not targeted by user id or default rollout @@ -350,7 +349,7 @@ public async Task TargetingAccessor() UserId = "Patty" }; - Assert.False(await featureManager.IsEnabledAsync(beta, CancellationToken.None)); + Assert.False(await featureManager.IsEnabledAsync(beta)); } [Fact] @@ -383,11 +382,11 @@ public async Task UsesContext() context.AccountId = "NotEnabledAccount"; - Assert.False(await featureManager.IsEnabledAsync(ContextualFeature, context, CancellationToken.None)); + Assert.False(await featureManager.IsEnabledAsync(ContextualFeature, context)); context.AccountId = "abc"; - Assert.True(await featureManager.IsEnabledAsync(ContextualFeature, context, CancellationToken.None)); + Assert.True(await featureManager.IsEnabledAsync(ContextualFeature, context)); } [Fact] @@ -429,7 +428,7 @@ public async Task ListsFeatures() bool hasItems = false; - await foreach (string feature in featureManager.GetFeatureNamesAsync(CancellationToken.None)) + await foreach (string feature in featureManager.GetFeatureNamesAsync()) { hasItems = true; @@ -455,8 +454,7 @@ public async Task ThrowsExceptionForMissingFeatureFilter() IFeatureManager featureManager = serviceProvider.GetRequiredService(); - FeatureManagementException e = await Assert.ThrowsAsync( - async () => await featureManager.IsEnabledAsync(ConditionalFeature, CancellationToken.None)); + FeatureManagementException e = await Assert.ThrowsAsync(async () => await featureManager.IsEnabledAsync(ConditionalFeature)); Assert.Equal(FeatureManagementError.MissingFeatureFilter, e.Error); } @@ -482,7 +480,7 @@ public async Task SwallowsExceptionForMissingFeatureFilter() IFeatureManager featureManager = serviceProvider.GetRequiredService(); - var isEnabled = await featureManager.IsEnabledAsync(ConditionalFeature, CancellationToken.None); + var isEnabled = await featureManager.IsEnabledAsync(ConditionalFeature); Assert.False(isEnabled); } @@ -535,7 +533,7 @@ public async Task CustomFeatureDefinitionProvider() return true; }; - await featureManager.IsEnabledAsync(ConditionalFeature, CancellationToken.None); + await featureManager.IsEnabledAsync(ConditionalFeature); Assert.True(called); } diff --git a/tests/Tests.FeatureManagement/InMemoryFeatureDefinitionProvider.cs b/tests/Tests.FeatureManagement/InMemoryFeatureDefinitionProvider.cs index 3aad4c8c..91cbcefd 100644 --- a/tests/Tests.FeatureManagement/InMemoryFeatureDefinitionProvider.cs +++ b/tests/Tests.FeatureManagement/InMemoryFeatureDefinitionProvider.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; namespace Tests.FeatureManagement @@ -18,7 +16,7 @@ public InMemoryFeatureDefinitionProvider(IEnumerable featureD } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public async IAsyncEnumerable GetAllFeatureDefinitionsAsync([EnumeratorCancellation] CancellationToken _) + public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { foreach (FeatureDefinition definition in _definitions) @@ -27,7 +25,7 @@ public async IAsyncEnumerable GetAllFeatureDefinitionsAsync([ } } - public Task GetFeatureDefinitionAsync(string featureName, CancellationToken _) + public Task GetFeatureDefinitionAsync(string featureName) { return Task.FromResult(_definitions.FirstOrDefault(definitions => definitions.Name.Equals(featureName, StringComparison.OrdinalIgnoreCase))); } diff --git a/tests/Tests.FeatureManagement/InvalidFeatureFilter.cs b/tests/Tests.FeatureManagement/InvalidFeatureFilter.cs index 4f81e3e2..0fab5063 100644 --- a/tests/Tests.FeatureManagement/InvalidFeatureFilter.cs +++ b/tests/Tests.FeatureManagement/InvalidFeatureFilter.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. // using Microsoft.FeatureManagement; -using System.Threading; using System.Threading.Tasks; namespace Tests.FeatureManagement @@ -11,12 +10,12 @@ namespace Tests.FeatureManagement // Cannot implement more than one IFeatureFilter interface class InvalidFeatureFilter : IContextualFeatureFilter, IContextualFeatureFilter { - public Task EvaluateAsync(FeatureFilterEvaluationContext context, IAccountContext accountContext, CancellationToken _) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, IAccountContext accountContext) { return Task.FromResult(false); } - public Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, object appContext, CancellationToken _) + public Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, object appContext) { return Task.FromResult(false); } @@ -26,12 +25,12 @@ public Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterCont // Cannot implement more than one IFeatureFilter interface class InvalidFeatureFilter2 : IFeatureFilter, IContextualFeatureFilter { - public Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, object appContext, CancellationToken _) + public Task EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, object appContext) { return Task.FromResult(false); } - public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken _) + public Task EvaluateAsync(FeatureFilterEvaluationContext context) { return Task.FromResult(false); } diff --git a/tests/Tests.FeatureManagement/OnDemandTargetingContextAccessor.cs b/tests/Tests.FeatureManagement/OnDemandTargetingContextAccessor.cs index aaff4a41..7eb7e971 100644 --- a/tests/Tests.FeatureManagement/OnDemandTargetingContextAccessor.cs +++ b/tests/Tests.FeatureManagement/OnDemandTargetingContextAccessor.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. // using Microsoft.FeatureManagement.FeatureFilters; -using System.Threading; using System.Threading.Tasks; namespace Tests.FeatureManagement @@ -11,7 +10,7 @@ class OnDemandTargetingContextAccessor : ITargetingContextAccessor { public TargetingContext Current { get; set; } - public ValueTask GetContextAsync(CancellationToken _) + public ValueTask GetContextAsync() { return new ValueTask(Current); } diff --git a/tests/Tests.FeatureManagement/TestFilter.cs b/tests/Tests.FeatureManagement/TestFilter.cs index be6e3be7..ed236742 100644 --- a/tests/Tests.FeatureManagement/TestFilter.cs +++ b/tests/Tests.FeatureManagement/TestFilter.cs @@ -3,7 +3,6 @@ // using Microsoft.FeatureManagement; using System; -using System.Threading; using System.Threading.Tasks; namespace Tests.FeatureManagement @@ -12,7 +11,7 @@ class TestFilter : IFeatureFilter { public Func Callback { get; set; } - public Task EvaluateAsync(FeatureFilterEvaluationContext context, CancellationToken _) + public Task EvaluateAsync(FeatureFilterEvaluationContext context) { return Task.FromResult(Callback?.Invoke(context) ?? false); } From 65a45519bebd21ed9e2560b07b3eb8136e641dbf Mon Sep 17 00:00:00 2001 From: Jimmy Campbell Date: Wed, 1 Sep 2021 18:29:31 -0400 Subject: [PATCH 03/10] Added option to throw when attempting to evaluate a missing feature. (#140) * Added option to throw for missing features. * Add default value documentation for feature management options. --- .../FeatureManagementError.cs | 7 +++++- .../FeatureManagementOptions.cs | 8 ++++++ .../FeatureManager.cs | 13 ++++++++++ .../FeatureManagement.cs | 25 +++++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManagementError.cs b/src/Microsoft.FeatureManagement/FeatureManagementError.cs index a1e3319f..1cdb9eed 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementError.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementError.cs @@ -16,6 +16,11 @@ public enum FeatureManagementError /// /// A feature filter configured for the feature being evaluated is an ambiguous reference to multiple registered feature filters. /// - AmbiguousFeatureFilter + AmbiguousFeatureFilter, + + /// + /// A feature that was requested for evaluation was not found. + /// + MissingFeature } } diff --git a/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs b/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs index 9322934e..e425c054 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs @@ -11,7 +11,15 @@ public class FeatureManagementOptions /// /// Controls the behavior of feature evaluation when dependent feature filters are missing. /// If missing feature filters are not ignored an exception will be thrown when attempting to evaluate a feature that depends on a missing feature filter. + /// The default value is false. /// public bool IgnoreMissingFeatureFilters { get; set; } + + /// + /// Controls the behavior of feature evaluation when the target feature is missing. + /// If missing features are not ignored an exception will be thrown when attempting to evaluate them. + /// The default value is true. + /// + public bool IgnoreMissingFeatures { get; set; } = true; } } diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 25873f50..74bd10ab 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -141,6 +141,19 @@ private async Task IsEnabledAsync(string feature, TContext appCo } } } + else + { + string errorMessage = $"The feature declaration for the feature '{feature}' was not found."; + + if (!_options.IgnoreMissingFeatures) + { + throw new FeatureManagementException(FeatureManagementError.MissingFeatureFilter, errorMessage); + } + else + { + _logger.LogWarning(errorMessage); + } + } foreach (ISessionManager sessionManager in _sessionManagers) { diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index e30888e4..d7e85366 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -485,6 +485,31 @@ public async Task SwallowsExceptionForMissingFeatureFilter() Assert.False(isEnabled); } + [Fact] + public async Task ThrowsForMissingFeatures() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + var services = new ServiceCollection(); + + services + .Configure(options => + { + options.IgnoreMissingFeatures = false; + }); + + services + .AddSingleton(config) + .AddFeatureManagement(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + FeatureManagementException fme = await Assert.ThrowsAsync(() => + featureManager.IsEnabledAsync("NonExistentFeature")); + } + [Fact] public async Task CustomFeatureDefinitionProvider() { From ee2a386fb5ff3507af893c5eb4cb536e41259993 Mon Sep 17 00:00:00 2001 From: Jimmy Campbell Date: Thu, 2 Sep 2021 13:11:10 -0400 Subject: [PATCH 04/10] Correct error type in exception. (#142) --- src/Microsoft.FeatureManagement/FeatureManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 74bd10ab..097af3a2 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -147,7 +147,7 @@ private async Task IsEnabledAsync(string feature, TContext appCo if (!_options.IgnoreMissingFeatures) { - throw new FeatureManagementException(FeatureManagementError.MissingFeatureFilter, errorMessage); + throw new FeatureManagementException(FeatureManagementError.MissingFeature, errorMessage); } else { From e366fd59776e3fb191d13ded2ab55b249637c187 Mon Sep 17 00:00:00 2001 From: Jimmy Campbell Date: Tue, 7 Sep 2021 17:00:53 -0400 Subject: [PATCH 05/10] Support concurrency in IFeatureManagerSnapshot (#141) * Make FeatureManagementSnapshot thread safe. * Add feature manager snapshot concurrency test. * Remove lazy usage. --- .../FeatureManagerSnapshot.cs | 37 +++-------- .../FeatureManagement.cs | 66 +++++++++++++++++-- tests/Tests.FeatureManagement/TestFilter.cs | 4 +- 3 files changed, 71 insertions(+), 36 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index a218e1cc..1c676cbb 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. // using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; @@ -13,7 +14,7 @@ namespace Microsoft.FeatureManagement class FeatureManagerSnapshot : IFeatureManagerSnapshot { private readonly IFeatureManager _featureManager; - private readonly IDictionary _flagCache = new Dictionary(); + private readonly ConcurrentDictionary> _flagCache = new ConcurrentDictionary>(); private IEnumerable _featureNames; public FeatureManagerSnapshot(IFeatureManager featureManager) @@ -41,36 +42,18 @@ public async IAsyncEnumerable GetFeatureNamesAsync() } } - public async Task IsEnabledAsync(string feature) + public Task IsEnabledAsync(string feature) { - // - // First, check local cache - if (_flagCache.ContainsKey(feature)) - { - return _flagCache[feature]; - } - - bool enabled = await _featureManager.IsEnabledAsync(feature).ConfigureAwait(false); - - _flagCache[feature] = enabled; - - return enabled; + return _flagCache.GetOrAdd( + feature, + (key) => _featureManager.IsEnabledAsync(key)); } - public async Task IsEnabledAsync(string feature, TContext context) + public Task IsEnabledAsync(string feature, TContext context) { - // - // First, check local cache - if (_flagCache.ContainsKey(feature)) - { - return _flagCache[feature]; - } - - bool enabled = await _featureManager.IsEnabledAsync(feature, context).ConfigureAwait(false); - - _flagCache[feature] = enabled; - - return enabled; + return _flagCache.GetOrAdd( + feature, + (key) => _featureManager.IsEnabledAsync(key, context)); } } } diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index d7e85366..2dade89c 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -63,7 +63,7 @@ public async Task ReadsConfiguration() Assert.Equal(ConditionalFeature, evaluationContext.FeatureName); - return true; + return Task.FromResult(true); }; await featureManager.IsEnabledAsync(ConditionalFeature); @@ -106,14 +106,14 @@ public async Task Integrates() TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter); - testFeatureFilter.Callback = _ => true; + testFeatureFilter.Callback = _ => Task.FromResult(true); HttpResponseMessage res = await testServer.CreateClient().GetAsync(""); Assert.True(res.Headers.Contains(nameof(MvcFilter))); Assert.True(res.Headers.Contains(nameof(RouterMiddleware))); - testFeatureFilter.Callback = _ => false; + testFeatureFilter.Callback = _ => Task.FromResult(false); res = await testServer.CreateClient().GetAsync(""); @@ -143,7 +143,7 @@ public async Task GatesFeatures() // // Enable all features - testFeatureFilter.Callback = ctx => true; + testFeatureFilter.Callback = ctx => Task.FromResult(true); HttpResponseMessage gateAllResponse = await testServer.CreateClient().GetAsync("gateAll"); HttpResponseMessage gateAnyResponse = await testServer.CreateClient().GetAsync("gateAny"); @@ -153,7 +153,7 @@ public async Task GatesFeatures() // // Enable 1/2 features - testFeatureFilter.Callback = ctx => ctx.FeatureName == Enum.GetName(typeof(Features), Features.ConditionalFeature); + testFeatureFilter.Callback = ctx => Task.FromResult(ctx.FeatureName == Enum.GetName(typeof(Features), Features.ConditionalFeature)); gateAllResponse = await testServer.CreateClient().GetAsync("gateAll"); gateAnyResponse = await testServer.CreateClient().GetAsync("gateAny"); @@ -163,7 +163,7 @@ public async Task GatesFeatures() // // Enable no - testFeatureFilter.Callback = ctx => false; + testFeatureFilter.Callback = ctx => Task.FromResult(false); gateAllResponse = await testServer.CreateClient().GetAsync("gateAll"); gateAnyResponse = await testServer.CreateClient().GetAsync("gateAny"); @@ -555,7 +555,7 @@ public async Task CustomFeatureDefinitionProvider() Assert.Equal(ConditionalFeature, evaluationContext.FeatureName); - return true; + return Task.FromResult(true); }; await featureManager.IsEnabledAsync(ConditionalFeature); @@ -563,6 +563,58 @@ public async Task CustomFeatureDefinitionProvider() Assert.True(called); } + [Fact] + public async Task ThreadsafeSnapshot() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + var services = new ServiceCollection(); + + services + .AddSingleton(config) + .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 = async (evaluationContext) => + { + called = true; + + await Task.Delay(10); + + return new Random().Next(0, 100) > 50; + }; + + var tasks = new List>(); + + for (int i = 0; i < 1000; i++) + { + tasks.Add(featureManager.IsEnabledAsync(ConditionalFeature)); + } + + Assert.True(called); + + await Task.WhenAll(tasks); + + bool result = tasks.First().Result; + + foreach (Task t in tasks) + { + Assert.Equal(result, t.Result); + } + } + private static void DisableEndpointRouting(MvcOptions options) { #if NET5_0 || NETCOREAPP3_1 diff --git a/tests/Tests.FeatureManagement/TestFilter.cs b/tests/Tests.FeatureManagement/TestFilter.cs index ed236742..d3db4225 100644 --- a/tests/Tests.FeatureManagement/TestFilter.cs +++ b/tests/Tests.FeatureManagement/TestFilter.cs @@ -9,11 +9,11 @@ namespace Tests.FeatureManagement { class TestFilter : IFeatureFilter { - public Func Callback { get; set; } + public Func> Callback { get; set; } public Task EvaluateAsync(FeatureFilterEvaluationContext context) { - return Task.FromResult(Callback?.Invoke(context) ?? false); + return Callback?.Invoke(context) ?? Task.FromResult(false); } } } From fc90e57810abc066f06cc2e40a9c7b839ea90037 Mon Sep 17 00:00:00 2001 From: brental <10455806+brental@users.noreply.github.com> Date: Mon, 20 Sep 2021 02:43:05 +1000 Subject: [PATCH 06/10] Updated FilterAliasAttribute to use nameof(alias) when throwing an ArgumentNullException when the alias param is null or empty. (#146) --- src/Microsoft.FeatureManagement/FilterAliasAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/FilterAliasAttribute.cs b/src/Microsoft.FeatureManagement/FilterAliasAttribute.cs index 42f5571a..30f9d997 100644 --- a/src/Microsoft.FeatureManagement/FilterAliasAttribute.cs +++ b/src/Microsoft.FeatureManagement/FilterAliasAttribute.cs @@ -18,7 +18,7 @@ public FilterAliasAttribute(string alias) { if (string.IsNullOrEmpty(alias)) { - throw new ArgumentNullException(alias); + throw new ArgumentNullException(nameof(alias)); } Alias = alias; From 4231727e6834c301b0dcfa658e43830534ecaf78 Mon Sep 17 00:00:00 2001 From: Jimmy Campbell Date: Mon, 20 Sep 2021 12:34:11 -0400 Subject: [PATCH 07/10] Add the capability to prevent features with feature filters from being evaluated. (#143) --- .../ConfigurationFeatureDefinitionProvider.cs | 14 +++++++++++- .../FeatureDefinition.cs | 6 +++++ .../FeatureManagementError.cs | 7 +++++- .../FeatureManager.cs | 5 +++-- .../FeatureManagement.cs | 22 +++++++++++++++++++ .../Tests.FeatureManagement/appsettings.json | 15 +++++++++++++ 6 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index 8763b850..44fea504 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -167,10 +167,22 @@ We support } } + bool evaluate = true; + + val = configurationSection[nameof(FeatureDefinition.Evaluate)]; + + if (!string.IsNullOrEmpty(val) && !bool.TryParse(val, out evaluate)) + { + throw new FeatureManagementException( + FeatureManagementError.InvalidConfiguration, + $"The feature '{configurationSection.Key}' has an invalid value for the '{nameof(FeatureDefinition.Evaluate)}' property."); + } + return new FeatureDefinition() { Name = configurationSection.Key, - EnabledFor = enabledFor + EnabledFor = enabledFor, + Evaluate = evaluate }; } diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index 4931fd5b..de02d2fb 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -19,5 +19,11 @@ public class FeatureDefinition /// The feature filters that the feature can be enabled for. /// public IEnumerable EnabledFor { get; set; } = new List(); + + /// + /// Dictates whether the feature should be evaluated. If false, the feature will be considered disabled. + /// The default value is true. + /// + public bool Evaluate { get; set; } = true; } } diff --git a/src/Microsoft.FeatureManagement/FeatureManagementError.cs b/src/Microsoft.FeatureManagement/FeatureManagementError.cs index 1cdb9eed..8b3f84bd 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementError.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementError.cs @@ -21,6 +21,11 @@ public enum FeatureManagementError /// /// A feature that was requested for evaluation was not found. /// - MissingFeature + MissingFeature, + + /// + /// An invalid configuration was encountered when performing a feature management operation. + /// + InvalidConfiguration } } diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 097af3a2..4aad0a52 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -74,7 +74,8 @@ private async Task IsEnabledAsync(string feature, TContext appCo FeatureDefinition featureDefinition = await _featureDefinitionProvider.GetFeatureDefinitionAsync(feature).ConfigureAwait(false); - if (featureDefinition != null) + if (featureDefinition != null + && featureDefinition.Evaluate) { // // Check if feature is always on @@ -141,7 +142,7 @@ private async Task IsEnabledAsync(string feature, TContext appCo } } } - else + else if (featureDefinition == null) { string errorMessage = $"The feature declaration for the feature '{feature}' was not found."; diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 2dade89c..a0caa6b9 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -26,6 +26,8 @@ public class FeatureManagement private const string OffFeature = "OffFeature"; private const string ConditionalFeature = "ConditionalFeature"; private const string ContextualFeature = "ContextualFeature"; + private const string AlwaysOnFeature = "AlwaysOnFeature"; + private const string NonEvaluatedAlwaysOnFeature = "NonEvaluatedAlwaysOnFeature"; [Fact] public async Task ReadsConfiguration() @@ -615,6 +617,26 @@ public async Task ThreadsafeSnapshot() } } + [Fact] + public async Task TogglesEvaluation() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + var services = new ServiceCollection(); + + services + .AddSingleton(config) + .AddFeatureManagement(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + Assert.True(await featureManager.IsEnabledAsync(AlwaysOnFeature)); + + Assert.False(await featureManager.IsEnabledAsync(NonEvaluatedAlwaysOnFeature)); + } + private static void DisableEndpointRouting(MvcOptions options) { #if NET5_0 || NETCOREAPP3_1 diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json index f033a400..0bfad5d4 100644 --- a/tests/Tests.FeatureManagement/appsettings.json +++ b/tests/Tests.FeatureManagement/appsettings.json @@ -63,6 +63,21 @@ } } ] + }, + "AlwaysOnFeature": { + "EnabledFor": [ + { + "Name": "AlwaysOn" + } + ] + }, + "NonEvaluatedAlwaysOnFeature": { + "Evaluate": false, + "EnabledFor": [ + { + "Name": "AlwaysOn" + } + ] } } } From 423501a59d8d086bbe60baa89dedcded1133213f Mon Sep 17 00:00:00 2001 From: Jimmy Campbell Date: Mon, 20 Sep 2021 14:27:58 -0400 Subject: [PATCH 08/10] Revert "Add the capability to prevent features with feature filters from being evaluated. (#143)" (#147) This reverts commit 4231727e6834c301b0dcfa658e43830534ecaf78. --- .../ConfigurationFeatureDefinitionProvider.cs | 14 +----------- .../FeatureDefinition.cs | 6 ----- .../FeatureManagementError.cs | 7 +----- .../FeatureManager.cs | 5 ++--- .../FeatureManagement.cs | 22 ------------------- .../Tests.FeatureManagement/appsettings.json | 15 ------------- 6 files changed, 4 insertions(+), 65 deletions(-) diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index 44fea504..8763b850 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -167,22 +167,10 @@ We support } } - bool evaluate = true; - - val = configurationSection[nameof(FeatureDefinition.Evaluate)]; - - if (!string.IsNullOrEmpty(val) && !bool.TryParse(val, out evaluate)) - { - throw new FeatureManagementException( - FeatureManagementError.InvalidConfiguration, - $"The feature '{configurationSection.Key}' has an invalid value for the '{nameof(FeatureDefinition.Evaluate)}' property."); - } - return new FeatureDefinition() { Name = configurationSection.Key, - EnabledFor = enabledFor, - Evaluate = evaluate + EnabledFor = enabledFor }; } diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index de02d2fb..4931fd5b 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -19,11 +19,5 @@ public class FeatureDefinition /// The feature filters that the feature can be enabled for. /// public IEnumerable EnabledFor { get; set; } = new List(); - - /// - /// Dictates whether the feature should be evaluated. If false, the feature will be considered disabled. - /// The default value is true. - /// - public bool Evaluate { get; set; } = true; } } diff --git a/src/Microsoft.FeatureManagement/FeatureManagementError.cs b/src/Microsoft.FeatureManagement/FeatureManagementError.cs index 8b3f84bd..1cdb9eed 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementError.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementError.cs @@ -21,11 +21,6 @@ public enum FeatureManagementError /// /// A feature that was requested for evaluation was not found. /// - MissingFeature, - - /// - /// An invalid configuration was encountered when performing a feature management operation. - /// - InvalidConfiguration + MissingFeature } } diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 4aad0a52..097af3a2 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -74,8 +74,7 @@ private async Task IsEnabledAsync(string feature, TContext appCo FeatureDefinition featureDefinition = await _featureDefinitionProvider.GetFeatureDefinitionAsync(feature).ConfigureAwait(false); - if (featureDefinition != null - && featureDefinition.Evaluate) + if (featureDefinition != null) { // // Check if feature is always on @@ -142,7 +141,7 @@ private async Task IsEnabledAsync(string feature, TContext appCo } } } - else if (featureDefinition == null) + else { string errorMessage = $"The feature declaration for the feature '{feature}' was not found."; diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index a0caa6b9..2dade89c 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -26,8 +26,6 @@ public class FeatureManagement private const string OffFeature = "OffFeature"; private const string ConditionalFeature = "ConditionalFeature"; private const string ContextualFeature = "ContextualFeature"; - private const string AlwaysOnFeature = "AlwaysOnFeature"; - private const string NonEvaluatedAlwaysOnFeature = "NonEvaluatedAlwaysOnFeature"; [Fact] public async Task ReadsConfiguration() @@ -617,26 +615,6 @@ public async Task ThreadsafeSnapshot() } } - [Fact] - public async Task TogglesEvaluation() - { - IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); - - var services = new ServiceCollection(); - - services - .AddSingleton(config) - .AddFeatureManagement(); - - ServiceProvider serviceProvider = services.BuildServiceProvider(); - - IFeatureManager featureManager = serviceProvider.GetRequiredService(); - - Assert.True(await featureManager.IsEnabledAsync(AlwaysOnFeature)); - - Assert.False(await featureManager.IsEnabledAsync(NonEvaluatedAlwaysOnFeature)); - } - private static void DisableEndpointRouting(MvcOptions options) { #if NET5_0 || NETCOREAPP3_1 diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json index 0bfad5d4..f033a400 100644 --- a/tests/Tests.FeatureManagement/appsettings.json +++ b/tests/Tests.FeatureManagement/appsettings.json @@ -63,21 +63,6 @@ } } ] - }, - "AlwaysOnFeature": { - "EnabledFor": [ - { - "Name": "AlwaysOn" - } - ] - }, - "NonEvaluatedAlwaysOnFeature": { - "Evaluate": false, - "EnabledFor": [ - { - "Name": "AlwaysOn" - } - ] } } } From 8d1774ff0521dbbf0f1f35551cc2d7115d1f65e9 Mon Sep 17 00:00:00 2001 From: Jimmy Campbell Date: Wed, 22 Sep 2021 14:38:07 -0400 Subject: [PATCH 09/10] Updated minor versions. (#148) --- .../Microsoft.FeatureManagement.AspNetCore.csproj | 2 +- .../Microsoft.FeatureManagement.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj b/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj index f0c31e0e..821ff397 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj +++ b/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj @@ -4,7 +4,7 @@ 2 - 3 + 4 0 diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj index 3bb53e2d..620a1264 100644 --- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj +++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj @@ -4,7 +4,7 @@ 2 - 3 + 4 0 From e3eb4dacc4b8238c23a8265bdb288ac72c2a3c10 Mon Sep 17 00:00:00 2001 From: Neil Johari Date: Fri, 3 Dec 2021 15:12:24 -0800 Subject: [PATCH 10/10] Fix PercentageFilter inequality bound (#156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NextDouble() ∈ [0, 1). We use a strict inequality, so a 0% value sporadically returns true as 0⩽0. The fix is to use a non-strict inequality, <. The upper bound still works: 0.999... < 1. --- .../FeatureFilters/PercentageFilter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs b/src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs index e642eb35..2ece90c2 100644 --- a/src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs +++ b/src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs @@ -46,7 +46,7 @@ public Task EvaluateAsync(FeatureFilterEvaluationContext context) if (result) { - result = (RandomGenerator.NextDouble() * 100) <= settings.Value; + result = (RandomGenerator.NextDouble() * 100) < settings.Value; } return Task.FromResult(result);