From c870ff5679f294c8ec9ac121ca7d5f7aaf7796dc Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Fri, 26 Jul 2024 12:14:11 +0800 Subject: [PATCH 01/11] add variant feature manager extensions --- .../FeatureManager.cs | 2 +- .../FeatureManagerSnapshot.cs | 2 +- .../IVariantFeatureManager.cs | 4 +-- .../VariantFeatureManagerExtensions.cs | 28 +++++++++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 40a0f0a7..25d0fcb3 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -228,7 +228,7 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke /// An instance of used to evaluate which variant the user will be assigned. /// The cancellation token to cancel the operation. /// A variant assigned to the user based on the feature's configured allocation. - public async ValueTask GetVariantAsync(string feature, TargetingContext context, CancellationToken cancellationToken = default) + public async ValueTask GetVariantAsync(string feature, ITargetingContext context, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(feature)) { diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index 6018931d..384bb2b1 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -97,7 +97,7 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke return variant; } - public async ValueTask GetVariantAsync(string feature, TargetingContext context, CancellationToken cancellationToken) + public async ValueTask GetVariantAsync(string feature, ITargetingContext context, CancellationToken cancellationToken) { string cacheKey = GetVariantCacheKey(feature); diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index 0b78a237..a460abfd 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -49,9 +49,9 @@ public interface IVariantFeatureManager /// Gets the assigned variant for a specific feature. /// /// The name of the feature to evaluate. - /// An instance of used to evaluate which variant the user will be assigned. + /// An instance of used to evaluate which variant the user will be assigned. /// The cancellation token to cancel the operation. /// A variant assigned to the user based on the feature's configured allocation. - ValueTask GetVariantAsync(string feature, TargetingContext context, CancellationToken cancellationToken = default); + ValueTask GetVariantAsync(string feature, ITargetingContext context, CancellationToken cancellationToken = default); } } diff --git a/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs b/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs new file mode 100644 index 00000000..1ef53c3a --- /dev/null +++ b/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using Microsoft.FeatureManagement.FeatureFilters; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.FeatureManagement +{ + /// + /// Extensions used to provide an overload for that accepts a type directly. + /// + public static class VariantFeatureManagerExtensions + { + /// + /// Gets the assigned variant for a specific feature. + /// + /// The instance. + /// The name of the feature to evaluate. + /// An instance of used to evaluate which variant the user will be assigned. + /// The cancellation token to cancel the operation. + /// A variant assigned to the user based on the feature's configured allocation. + public static ValueTask GetVariantAsync(this IVariantFeatureManager variantFeatureManager, string feature, TargetingContext context, CancellationToken cancellationToken = default) + { + return variantFeatureManager.GetVariantAsync(feature, context, cancellationToken); + } + } +} From 8ea84fb707be9be92257da107e5f4d723c058952 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 29 Jul 2024 17:04:07 +0800 Subject: [PATCH 02/11] use TContext to avoid future breaking change --- src/Microsoft.FeatureManagement/FeatureManager.cs | 10 +++++----- .../FeatureManagerSnapshot.cs | 2 +- .../IVariantFeatureManager.cs | 4 ++-- .../Telemetry/EvaluationEvent.cs | 2 +- .../VariantFeatureManagerExtensions.cs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 25d0fcb3..d8b94850 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -225,10 +225,10 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke /// Gets the assigned variant for a specific feature. /// /// The name of the feature to evaluate. - /// An instance of used to evaluate which variant the user will be assigned. + /// A context providing information that can used to evaluate which variant the user will be assigned. /// The cancellation token to cancel the operation. /// A variant assigned to the user based on the feature's configured allocation. - public async ValueTask GetVariantAsync(string feature, ITargetingContext context, CancellationToken cancellationToken = default) + public async ValueTask GetVariantAsync(string feature, TContext context, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(feature)) { @@ -262,11 +262,11 @@ private async ValueTask EvaluateFeature(string featur // // Determine Targeting Context - TargetingContext targetingContext; + ITargetingContext targetingContext; if (useContext) { - targetingContext = context as TargetingContext; + targetingContext = context as ITargetingContext; } else { @@ -601,7 +601,7 @@ private async ValueTask ResolveTargetingContextAsync(Cancellat return context; } - private ValueTask AssignVariantAsync(EvaluationEvent evaluationEvent, TargetingContext targetingContext, CancellationToken cancellationToken) + private ValueTask AssignVariantAsync(EvaluationEvent evaluationEvent, ITargetingContext targetingContext, CancellationToken cancellationToken) { Debug.Assert(evaluationEvent != null); diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index 384bb2b1..a6b26910 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -97,7 +97,7 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke return variant; } - public async ValueTask GetVariantAsync(string feature, ITargetingContext context, CancellationToken cancellationToken) + public async ValueTask GetVariantAsync(string feature, TContext context, CancellationToken cancellationToken) { string cacheKey = GetVariantCacheKey(feature); diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index a460abfd..600c224b 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -49,9 +49,9 @@ public interface IVariantFeatureManager /// Gets the assigned variant for a specific feature. /// /// The name of the feature to evaluate. - /// An instance of used to evaluate which variant the user will be assigned. + /// A context providing information that can used to evaluate which variant the user will be assigned. /// The cancellation token to cancel the operation. /// A variant assigned to the user based on the feature's configured allocation. - ValueTask GetVariantAsync(string feature, ITargetingContext context, CancellationToken cancellationToken = default); + ValueTask GetVariantAsync(string feature, TContext context, CancellationToken cancellationToken = default); } } diff --git a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs index 27a00329..ae95e4fc 100644 --- a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs +++ b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs @@ -18,7 +18,7 @@ public class EvaluationEvent /// /// The targeting context used to evaluate the feature. /// - public TargetingContext TargetingContext { get; set; } + public ITargetingContext TargetingContext { get; set; } /// /// The enabled state of the feature after evaluation. diff --git a/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs b/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs index 1ef53c3a..cc987ae7 100644 --- a/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs +++ b/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs @@ -8,7 +8,7 @@ namespace Microsoft.FeatureManagement { /// - /// Extensions used to provide an overload for that accepts a type directly. + /// Extensions used to provide an overload for that accepts a type directly. /// public static class VariantFeatureManagerExtensions { From ea685172137ed9ac6a92d1b3202c5edca37ec0d7 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 12 Aug 2024 12:51:34 +0800 Subject: [PATCH 03/11] TargetingContext in EvaluationEvent --- .../FeatureManager.cs | 17 ++++--- .../Telemetry/EvaluationEvent.cs | 2 +- .../FeatureManagementTest.cs | 47 +++++++++++++++++++ tests/Tests.FeatureManagement/Features.cs | 1 + .../Tests.FeatureManagement/appsettings.json | 28 +++++++++++ 5 files changed, 88 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index d8b94850..326651aa 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -216,7 +216,7 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke throw new ArgumentNullException(nameof(feature)); } - EvaluationEvent evaluationEvent = await EvaluateFeature(feature, context: null, useContext: false, cancellationToken); + EvaluationEvent evaluationEvent = await EvaluateFeature(feature, context: null, useContext: false, cancellationToken); return evaluationEvent.Variant; } @@ -262,13 +262,18 @@ private async ValueTask EvaluateFeature(string featur // // Determine Targeting Context - ITargetingContext targetingContext; + TargetingContext targetingContext = null; - if (useContext) + if (useContext && context is ITargetingContext targetingInfo) { - targetingContext = context as ITargetingContext; + targetingContext = new TargetingContext + { + UserId = targetingInfo.UserId, + Groups = targetingInfo.Groups + }; } - else + + if (targetingContext == null) { targetingContext = await ResolveTargetingContextAsync(cancellationToken).ConfigureAwait(false); } @@ -314,7 +319,7 @@ private async ValueTask EvaluateFeature(string featur if (useContext) { - message = $"A {nameof(TargetingContext)} required for variant assignment was not provided."; + message = $"A {nameof(ITargetingContext)} required for variant assignment was not provided."; } else if (TargetingContextAccessor == null) { diff --git a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs index ae95e4fc..27a00329 100644 --- a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs +++ b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs @@ -18,7 +18,7 @@ public class EvaluationEvent /// /// The targeting context used to evaluate the feature. /// - public ITargetingContext TargetingContext { get; set; } + public TargetingContext TargetingContext { get; set; } /// /// The enabled state of the feature after evaluation. diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs index e1a6efe1..77203120 100644 --- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs +++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs @@ -1641,6 +1641,53 @@ public async Task VariantBasedInjection() } ); } + + [Fact] + public async Task VariantFeatureFlagWithContextualFeatureFilter() + { + IConfiguration configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .Build(); + + IServiceCollection services = new ServiceCollection(); + + services.AddSingleton(configuration) + .AddFeatureManagement() + .AddFeatureFilter(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + ContextualTestFilter contextualTestFeatureFilter = (ContextualTestFilter)serviceProvider.GetRequiredService>().First(f => f is ContextualTestFilter); + + contextualTestFeatureFilter.ContextualCallback = (ctx, accountContext) => + { + var allowedAccounts = new List(); + + ctx.Parameters.Bind("AllowedAccounts", allowedAccounts); + + return allowedAccounts.Contains(accountContext.AccountId); + }; + + IVariantFeatureManager featureManager = serviceProvider.GetRequiredService(); + + AppContext context = new AppContext(); + + context.AccountId = "NotEnabledAccount"; + + Assert.False(await featureManager.IsEnabledAsync(Features.ContextualFeatureWithVariant, context)); + + Variant variant = await featureManager.GetVariantAsync(Features.ContextualFeatureWithVariant, context); + + Assert.Equal("Small", variant.Name); + + context.AccountId = "abc"; + + Assert.True(await featureManager.IsEnabledAsync(Features.ContextualFeatureWithVariant, context)); + + variant = await featureManager.GetVariantAsync(Features.ContextualFeatureWithVariant, context); + + Assert.Equal("Big", variant.Name); + } } public class FeatureManagementTelemetryTest diff --git a/tests/Tests.FeatureManagement/Features.cs b/tests/Tests.FeatureManagement/Features.cs index 51eef015..9562c9bb 100644 --- a/tests/Tests.FeatureManagement/Features.cs +++ b/tests/Tests.FeatureManagement/Features.cs @@ -30,5 +30,6 @@ static class Features public const string VariantImplementationFeature = "VariantImplementationFeature"; public const string OnTelemetryTestFeature = "OnTelemetryTestFeature"; public const string OffTelemetryTestFeature = "OffTelemetryTestFeature"; + public const string ContextualFeatureWithVariant = "ContextualFeatureWithVariant"; } } diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json index cf72cce4..0556fdf0 100644 --- a/tests/Tests.FeatureManagement/appsettings.json +++ b/tests/Tests.FeatureManagement/appsettings.json @@ -530,6 +530,34 @@ "telemetry": { "enabled": true } + }, + { + "id": "ContextualFeatureWithVariant", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "ContextualTest", + "parameters": { + "AllowedAccounts": [ + "abc" + ] + } + } + ] + }, + "variants": [ + { + "name": "Big" + }, + { + "name": "Small" + } + ], + "allocation": { + "default_when_enabled": "Big", + "default_when_disabled": "Small" + } } ] } From 0bdf279ed47c79903da3fdc88c16425d8249f7cb Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 12 Aug 2024 16:41:16 +0800 Subject: [PATCH 04/11] update comments & not use ambient context in contextual case --- .../FeatureManager.cs | 17 ++++++++--------- .../IFeatureManager.cs | 2 +- .../IVariantFeatureManager.cs | 5 ++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 326651aa..4124a744 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -144,7 +144,7 @@ public async 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. + /// A context that provides information that can be used to evaluate whether a feature should be on or off. /// True if the feature is enabled, otherwise false. public async Task IsEnabledAsync(string feature, TContext appContext) { @@ -170,7 +170,7 @@ public async ValueTask IsEnabledAsync(string feature, CancellationToken ca /// 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. + /// A context that provides 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. public async ValueTask IsEnabledAsync(string feature, TContext appContext, CancellationToken cancellationToken = default) @@ -225,7 +225,7 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke /// Gets the assigned variant for a specific feature. /// /// The name of the feature to evaluate. - /// A context providing information that can used to evaluate which variant the user will be assigned. + /// A context that provides information that can used to evaluate which variant the user will be assigned. /// The cancellation token to cancel the operation. /// A variant assigned to the user based on the feature's configured allocation. public async ValueTask GetVariantAsync(string feature, TContext context, CancellationToken cancellationToken = default) @@ -264,7 +264,11 @@ private async ValueTask EvaluateFeature(string featur // Determine Targeting Context TargetingContext targetingContext = null; - if (useContext && context is ITargetingContext targetingInfo) + if (!useContext) + { + targetingContext = await ResolveTargetingContextAsync(cancellationToken).ConfigureAwait(false); + } + else if (context is ITargetingContext targetingInfo) { targetingContext = new TargetingContext { @@ -273,11 +277,6 @@ private async ValueTask EvaluateFeature(string featur }; } - if (targetingContext == null) - { - targetingContext = await ResolveTargetingContextAsync(cancellationToken).ConfigureAwait(false); - } - evaluationEvent.TargetingContext = targetingContext; if (evaluationEvent.FeatureDefinition != null) diff --git a/src/Microsoft.FeatureManagement/IFeatureManager.cs b/src/Microsoft.FeatureManagement/IFeatureManager.cs index 1b4ea0cf..d8acc7f1 100644 --- a/src/Microsoft.FeatureManagement/IFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IFeatureManager.cs @@ -28,7 +28,7 @@ public interface IFeatureManager /// 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. + /// A context that provides information that can be used to evaluate whether a feature should be on or off. /// True if the feature is enabled, otherwise false. Task IsEnabledAsync(string feature, TContext context); } diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index 600c224b..a92451cb 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -using Microsoft.FeatureManagement.FeatureFilters; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -32,7 +31,7 @@ public interface IVariantFeatureManager /// 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. + /// A context that provides 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. ValueTask IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken = default); @@ -49,7 +48,7 @@ public interface IVariantFeatureManager /// Gets the assigned variant for a specific feature. /// /// The name of the feature to evaluate. - /// A context providing information that can used to evaluate which variant the user will be assigned. + /// A context that provides information that can used to evaluate which variant the user will be assigned. /// The cancellation token to cancel the operation. /// A variant assigned to the user based on the feature's configured allocation. ValueTask GetVariantAsync(string feature, TContext context, CancellationToken cancellationToken = default); From b7a2af80dab7e1bf174d959ff25eacd5d0eaa6a3 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Tue, 13 Aug 2024 12:56:50 +0800 Subject: [PATCH 05/11] update comment --- src/Microsoft.FeatureManagement/FeatureManager.cs | 2 +- src/Microsoft.FeatureManagement/IVariantFeatureManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 4124a744..346e758e 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -225,7 +225,7 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke /// Gets the assigned variant for a specific feature. /// /// The name of the feature to evaluate. - /// A context that provides information that can used to evaluate which variant the user will be assigned. + /// A context that provides information to evaluate which variant will be assigned to the user. /// The cancellation token to cancel the operation. /// A variant assigned to the user based on the feature's configured allocation. public async ValueTask GetVariantAsync(string feature, TContext context, CancellationToken cancellationToken = default) diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index a92451cb..5ca2c332 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -48,7 +48,7 @@ public interface IVariantFeatureManager /// Gets the assigned variant for a specific feature. /// /// The name of the feature to evaluate. - /// A context that provides information that can used to evaluate which variant the user will be assigned. + /// A context that provides information to evaluate which variant will be assigned to the user. /// The cancellation token to cancel the operation. /// A variant assigned to the user based on the feature's configured allocation. ValueTask GetVariantAsync(string feature, TContext context, CancellationToken cancellationToken = default); From e6124f6e369bb9c3f203dcb25c150d04de52caad Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 15 Aug 2024 13:22:14 +0800 Subject: [PATCH 06/11] use ITargetingContext & check runtime context type --- src/Microsoft.FeatureManagement/FeatureManager.cs | 12 ++++++------ .../FeatureManagerSnapshot.cs | 2 +- src/Microsoft.FeatureManagement/IFeatureManager.cs | 2 +- .../IVariantFeatureManager.cs | 5 +++-- tests/Tests.FeatureManagement/AppContext.cs | 9 ++++++++- .../Tests.FeatureManagement/ContextualTestFilter.cs | 6 +++--- .../Tests.FeatureManagement/FeatureManagementTest.cs | 2 +- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 346e758e..f512d109 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -144,7 +144,7 @@ public async Task IsEnabledAsync(string feature) /// Checks whether a given feature is enabled. /// /// The name of the feature to check. - /// A context that provides information that can be used to evaluate whether a feature should be on or off. + /// A context that provides information to evaluate whether a feature should be on or off. /// True if the feature is enabled, otherwise false. public async Task IsEnabledAsync(string feature, TContext appContext) { @@ -170,7 +170,7 @@ public async ValueTask IsEnabledAsync(string feature, CancellationToken ca /// Checks whether a given feature is enabled. /// /// The name of the feature to check. - /// A context that provides information that can be used to evaluate whether a feature should be on or off. + /// A context that provides information 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. public async ValueTask IsEnabledAsync(string feature, TContext appContext, CancellationToken cancellationToken = default) @@ -228,7 +228,7 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke /// A context that provides information to evaluate which variant will be assigned to the user. /// The cancellation token to cancel the operation. /// A variant assigned to the user based on the feature's configured allocation. - public async ValueTask GetVariantAsync(string feature, TContext context, CancellationToken cancellationToken = default) + public async ValueTask GetVariantAsync(string feature, ITargetingContext context, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(feature)) { @@ -318,7 +318,7 @@ private async ValueTask EvaluateFeature(string featur if (useContext) { - message = $"A {nameof(ITargetingContext)} required for variant assignment was not provided."; + message = $"The context of type {context.GetType().Name} does not implement {nameof(ITargetingContext)} for variant assignment."; } else if (TargetingContextAccessor == null) { @@ -500,7 +500,7 @@ private async ValueTask IsEnabledAsync(FeatureDefinition feature if (useAppContext) { - filter = GetFeatureFilterMetadata(featureFilterConfiguration.Name, typeof(TContext)) ?? + filter = GetFeatureFilterMetadata(featureFilterConfiguration.Name, appContext.GetType()) ?? GetFeatureFilterMetadata(featureFilterConfiguration.Name); } else @@ -542,7 +542,7 @@ private async ValueTask IsEnabledAsync(FeatureDefinition feature // IContextualFeatureFilter if (useAppContext) { - ContextualFeatureFilterEvaluator contextualFilter = GetContextualFeatureFilter(featureFilterConfiguration.Name, typeof(TContext)); + ContextualFeatureFilterEvaluator contextualFilter = GetContextualFeatureFilter(featureFilterConfiguration.Name, appContext.GetType()); if (contextualFilter != null && await contextualFilter.EvaluateAsync(context, appContext).ConfigureAwait(false) == targetEvaluation) diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index a6b26910..384bb2b1 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -97,7 +97,7 @@ public async ValueTask GetVariantAsync(string feature, CancellationToke return variant; } - public async ValueTask GetVariantAsync(string feature, TContext context, CancellationToken cancellationToken) + public async ValueTask GetVariantAsync(string feature, ITargetingContext context, CancellationToken cancellationToken) { string cacheKey = GetVariantCacheKey(feature); diff --git a/src/Microsoft.FeatureManagement/IFeatureManager.cs b/src/Microsoft.FeatureManagement/IFeatureManager.cs index d8acc7f1..5f1006f5 100644 --- a/src/Microsoft.FeatureManagement/IFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IFeatureManager.cs @@ -28,7 +28,7 @@ public interface IFeatureManager /// Checks whether a given feature is enabled. /// /// The name of the feature to check. - /// A context that provides information that can be used to evaluate whether a feature should be on or off. + /// A context that provides information to evaluate whether a feature should be on or off. /// True if the feature is enabled, otherwise false. Task IsEnabledAsync(string feature, TContext context); } diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index 5ca2c332..505b6652 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // +using Microsoft.FeatureManagement.FeatureFilters; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -31,7 +32,7 @@ public interface IVariantFeatureManager /// Checks whether a given feature is enabled. /// /// The name of the feature to check. - /// A context that provides information that can be used to evaluate whether a feature should be on or off. + /// A context that provides information 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. ValueTask IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken = default); @@ -51,6 +52,6 @@ public interface IVariantFeatureManager /// A context that provides information to evaluate which variant will be assigned to the user. /// The cancellation token to cancel the operation. /// A variant assigned to the user based on the feature's configured allocation. - ValueTask GetVariantAsync(string feature, TContext context, CancellationToken cancellationToken = default); + ValueTask GetVariantAsync(string feature, ITargetingContext context, CancellationToken cancellationToken = default); } } diff --git a/tests/Tests.FeatureManagement/AppContext.cs b/tests/Tests.FeatureManagement/AppContext.cs index 5431ac52..a55538e5 100644 --- a/tests/Tests.FeatureManagement/AppContext.cs +++ b/tests/Tests.FeatureManagement/AppContext.cs @@ -1,10 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // +using Microsoft.FeatureManagement.FeatureFilters; +using System.Collections.Generic; + namespace Tests.FeatureManagement { - class AppContext : IAccountContext + class AppContext : IAccountContext, ITargetingContext { public string AccountId { get; set; } + + public string UserId { get; set; } + + public IEnumerable Groups { get; set; } } } diff --git a/tests/Tests.FeatureManagement/ContextualTestFilter.cs b/tests/Tests.FeatureManagement/ContextualTestFilter.cs index 4bad3010..437bea54 100644 --- a/tests/Tests.FeatureManagement/ContextualTestFilter.cs +++ b/tests/Tests.FeatureManagement/ContextualTestFilter.cs @@ -7,11 +7,11 @@ namespace Tests.FeatureManagement { - class ContextualTestFilter : IContextualFeatureFilter + class ContextualTestFilter : IContextualFeatureFilter { - public Func ContextualCallback { get; set; } + public Func ContextualCallback { get; set; } - public Task EvaluateAsync(FeatureFilterEvaluationContext context, IAccountContext accountContext) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, AppContext accountContext) { return Task.FromResult(ContextualCallback?.Invoke(context, accountContext) ?? false); } diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs index 77203120..dda106ac 100644 --- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs +++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs @@ -1670,7 +1670,7 @@ public async Task VariantFeatureFlagWithContextualFeatureFilter() IVariantFeatureManager featureManager = serviceProvider.GetRequiredService(); - AppContext context = new AppContext(); + var context = new AppContext(); context.AccountId = "NotEnabledAccount"; From 19e5520da89cb0e1a31ceb7d9832823f4591d153 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 15 Aug 2024 13:24:32 +0800 Subject: [PATCH 07/11] use TargetingCotnext for private method --- 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 f512d109..d9e8a99d 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -605,7 +605,7 @@ private async ValueTask ResolveTargetingContextAsync(Cancellat return context; } - private ValueTask AssignVariantAsync(EvaluationEvent evaluationEvent, ITargetingContext targetingContext, CancellationToken cancellationToken) + private ValueTask AssignVariantAsync(EvaluationEvent evaluationEvent, TargetingContext targetingContext, CancellationToken cancellationToken) { Debug.Assert(evaluationEvent != null); From 6bcca05e71a73d1cc1a4ac397dd8c3b5e41ca538 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 15 Aug 2024 13:27:51 +0800 Subject: [PATCH 08/11] use var instead of type --- tests/Tests.FeatureManagement/FeatureManagementTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs index dda106ac..48551376 100644 --- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs +++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs @@ -503,7 +503,7 @@ public async Task UsesContext() IFeatureManager featureManager = provider.GetRequiredService(); - AppContext context = new AppContext(); + var context = new AppContext(); context.AccountId = "NotEnabledAccount"; From a30891663e6c77cd1c29508b9059d16e574ed10a Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 15 Aug 2024 13:42:10 +0800 Subject: [PATCH 09/11] revert change on ContextualTestFilter --- tests/Tests.FeatureManagement/ContextualTestFilter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Tests.FeatureManagement/ContextualTestFilter.cs b/tests/Tests.FeatureManagement/ContextualTestFilter.cs index 437bea54..4bad3010 100644 --- a/tests/Tests.FeatureManagement/ContextualTestFilter.cs +++ b/tests/Tests.FeatureManagement/ContextualTestFilter.cs @@ -7,11 +7,11 @@ namespace Tests.FeatureManagement { - class ContextualTestFilter : IContextualFeatureFilter + class ContextualTestFilter : IContextualFeatureFilter { - public Func ContextualCallback { get; set; } + public Func ContextualCallback { get; set; } - public Task EvaluateAsync(FeatureFilterEvaluationContext context, AppContext accountContext) + public Task EvaluateAsync(FeatureFilterEvaluationContext context, IAccountContext accountContext) { return Task.FromResult(ContextualCallback?.Invoke(context, accountContext) ?? false); } From 5a8bf514a18f24e667046d85536e16ae2e3f2bd5 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 15 Aug 2024 13:54:32 +0800 Subject: [PATCH 10/11] update comment --- .../VariantFeatureManagerExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs b/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs index cc987ae7..1ef53c3a 100644 --- a/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs +++ b/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs @@ -8,7 +8,7 @@ namespace Microsoft.FeatureManagement { /// - /// Extensions used to provide an overload for that accepts a type directly. + /// Extensions used to provide an overload for that accepts a type directly. /// public static class VariantFeatureManagerExtensions { From 5153e88b4291968b4b6fca0fb9ad3adc400c4381 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 26 Aug 2024 10:56:55 +0800 Subject: [PATCH 11/11] update comment --- .../VariantFeatureManagerExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs b/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs index 1ef53c3a..97385729 100644 --- a/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs +++ b/src/Microsoft.FeatureManagement/VariantFeatureManagerExtensions.cs @@ -8,7 +8,7 @@ namespace Microsoft.FeatureManagement { /// - /// Extensions used to provide an overload for that accepts a type directly. + /// Extensions for . /// public static class VariantFeatureManagerExtensions {