From 194295fe088a059c9017abee717f8f88a4cdd308 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 20 Mar 2024 16:12:13 +0800 Subject: [PATCH 01/10] add variant for feature tag helper --- .../TagHelpers/FeatureTagHelper.cs | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs index 4a0da1e0..bce5f72f 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. // using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Logging; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -13,7 +15,7 @@ namespace Microsoft.FeatureManagement.Mvc.TagHelpers /// public class FeatureTagHelper : TagHelper { - private readonly IFeatureManager _featureManager; + private readonly IVariantFeatureManager _featureManager; /// /// A feature name, or comma separated list of feature names, for which the content should be rendered. By default, all specified features must be enabled to render the content, but this requirement can be controlled by the property. @@ -30,13 +32,19 @@ public class FeatureTagHelper : TagHelper /// public bool Negate { get; set; } + /// + /// A variant name, or comma separated list of variant names. If any of specified variants is assigned, the content should be rendered. + /// If variant is specified, and will be ignored. Besides, should be exactly one feature name. + /// + public string Variant { get; set; } + /// /// Creates a feature tag helper. /// /// The feature manager snapshot to use to evaluate feature state. - public FeatureTagHelper(IFeatureManagerSnapshot featureManager) + public FeatureTagHelper(IVariantFeatureManagerSnapshot featureManager) { - _featureManager = featureManager; + _featureManager = featureManager ?? throw new ArgumentNullException(nameof(featureManager)); } /// @@ -52,16 +60,34 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu if (!string.IsNullOrEmpty(Name)) { - IEnumerable names = Name.Split(',').Select(n => n.Trim()); + IEnumerable features = Name.Split(',').Select(n => n.Trim()); - enabled = Requirement == RequirementType.All ? - await names.All(async n => await _featureManager.IsEnabledAsync(n).ConfigureAwait(false)) : - await names.Any(async n => await _featureManager.IsEnabledAsync(n).ConfigureAwait(false)); - } + if (string.IsNullOrEmpty(Variant)) + { + enabled = Requirement == RequirementType.All ? + await features.All(async feature => await _featureManager.IsEnabledAsync(feature).ConfigureAwait(false)) : + await features.Any(async feature => await _featureManager.IsEnabledAsync(feature).ConfigureAwait(false)); - if (Negate) - { - enabled = !enabled; + if (Negate) + { + enabled = !enabled; + } + } + else + { + if (features.Count() != 1) + { + throw new ArgumentException("Variant cannot be associated with multiple feature flags.", nameof(Name)); + } + + IEnumerable variants = Variant.Split(',').Select(n => n.Trim()); + + enabled = await variants.Any(async variant => { + Variant assignedVariant = await _featureManager.GetVariantAsync(features.First()); + + return variant == assignedVariant?.Name; + }); + } } if (!enabled) From 5bbeef647d6ed82ea3cb149af3f45c369d81eae0 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 20 Mar 2024 16:22:20 +0800 Subject: [PATCH 02/10] update --- .../TagHelpers/FeatureTagHelper.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs index bce5f72f..9db8a667 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. // using Microsoft.AspNetCore.Razor.TagHelpers; -using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -82,11 +81,12 @@ await features.All(async feature => await _featureManager.IsEnabledAsync(feature IEnumerable variants = Variant.Split(',').Select(n => n.Trim()); - enabled = await variants.Any(async variant => { - Variant assignedVariant = await _featureManager.GetVariantAsync(features.First()); + enabled = await variants.Any( + async variant => { + Variant assignedVariant = await _featureManager.GetVariantAsync(features.First()); - return variant == assignedVariant?.Name; - }); + return variant == assignedVariant?.Name; + }); } } From 217372de3465b097f9df5ca3c4a2f1dcb08064ab Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 25 Mar 2024 11:05:06 +0800 Subject: [PATCH 03/10] Negate also applies for variant --- .../TagHelpers/FeatureTagHelper.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs index 9db8a667..0a6c9943 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs @@ -66,11 +66,6 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu enabled = Requirement == RequirementType.All ? await features.All(async feature => await _featureManager.IsEnabledAsync(feature).ConfigureAwait(false)) : await features.Any(async feature => await _featureManager.IsEnabledAsync(feature).ConfigureAwait(false)); - - if (Negate) - { - enabled = !enabled; - } } else { @@ -90,6 +85,11 @@ await features.All(async feature => await _featureManager.IsEnabledAsync(feature } } + if (Negate) + { + enabled = !enabled; + } + if (!enabled) { output.SuppressOutput(); From 64134966f960c914ef81d051894ddd94e46473b0 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 28 Mar 2024 11:20:11 +0800 Subject: [PATCH 04/10] correct comment --- .../TagHelpers/FeatureTagHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs index 0a6c9943..f0b791e8 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs @@ -33,7 +33,7 @@ public class FeatureTagHelper : TagHelper /// /// A variant name, or comma separated list of variant names. If any of specified variants is assigned, the content should be rendered. - /// If variant is specified, and will be ignored. Besides, should be exactly one feature name. + /// If variant is specified, will be ignored. Besides, should be exactly one feature name. /// public string Variant { get; set; } From b025815e5eeef46dc124a817565fba9993fc0eb1 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 3 Apr 2024 10:07:27 +0800 Subject: [PATCH 05/10] update comment --- .../TagHelpers/FeatureTagHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs index f0b791e8..a6652e46 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs @@ -33,7 +33,7 @@ public class FeatureTagHelper : TagHelper /// /// A variant name, or comma separated list of variant names. If any of specified variants is assigned, the content should be rendered. - /// If variant is specified, will be ignored. Besides, should be exactly one feature name. + /// If variant is specified, must contain only one feature name and will have no effect. /// public string Variant { get; set; } From 6bf9148d0902e520e635518867faf7c2806774a2 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Sun, 7 Apr 2024 17:48:28 +0800 Subject: [PATCH 06/10] update README --- README.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2de4a3b8..1d99eb89 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,7 @@ public interface IDisabledFeaturesHandler ### View -In MVC views `` tags can be used to conditionally render content based on whether a feature is enabled or not. +In MVC views `` tags can be used to conditionally render content based on whether a feature is enabled or whether specific variant of a feature is assigned. ``` HTML+Razor @@ -340,7 +340,13 @@ In MVC views `` tags can be used to conditionally render content based ``` -You can also negate the tag helper evaluation to display content when a feature or set of features are disabled. By setting `negate="true"` in the example below, the content is only rendered if `FeatureX` is disabled. +``` HTML+Razor + +

This can only be seen if variant 'Alpha' of 'FeatureX' is assigned.

+
+``` + +You can also negate the tag helper evaluation to display content when a feature is disabled or when a variant is not assigned, by setting `negate="true"`. ``` HTML+Razor @@ -348,7 +354,13 @@ You can also negate the tag helper evaluation to display content when a feature ``` -The `` tag can reference multiple features by specifying a comma separated list of features in the `name` attribute. +``` HTML+Razor + +

This can only be seen if variant 'Alpha' of 'FeatureX' is not assigned.

+
+``` + +The `` tag can reference multiple features/variants by specifying a comma separated list of features/variants in the `name`/`variant` attribute. Note that if `variant` is specified, only *one* feature should be specified. ``` HTML+Razor @@ -356,7 +368,15 @@ The `` tag can reference multiple features by specifying a comma separa ``` -By default, all listed features must be enabled for the feature tag to be rendered. This behavior can be overidden by adding the `requirement` attribute as seen in the example below. +``` HTML+Razor + +

This can only be seen if variant 'Alpha' or 'Beta' of 'FeatureX' is assigned.

+
+``` + +**Note:** if `variant` is specified, only *one* feature should be specified. + +By default, all listed features must be enabled for the feature tag to be rendered. This behavior can be overidden by adding the `requirement` attribute as seen in the example below. Note that the `requirement` attribute will have no effect on `variant`. ``` HTML+Razor From 645f4c4bf47c6ad953e289a8e771b1eb442ba825 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 15 Apr 2024 17:20:29 +0800 Subject: [PATCH 07/10] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d99eb89..9cde2423 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,7 @@ public interface IDisabledFeaturesHandler ### View -In MVC views `` tags can be used to conditionally render content based on whether a feature is enabled or whether specific variant of a feature is assigned. +In MVC views `` tags can be used to conditionally render content based on whether a feature is enabled or whether specific variant of a feature is assigned. For more information about variant, please refer to the [variants](./README.md#Variants) section. ``` HTML+Razor From ab46852c9734c43da05c32e937bf228d5491a2f2 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Wed, 1 May 2024 10:34:33 +0800 Subject: [PATCH 08/10] throw exception for Requirement All --- .../TagHelpers/FeatureTagHelper.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs index a6652e46..3eff1ad5 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs @@ -76,9 +76,14 @@ await features.All(async feature => await _featureManager.IsEnabledAsync(feature IEnumerable variants = Variant.Split(',').Select(n => n.Trim()); + if (variants.Count() != 1 && Requirement == RequirementType.All) + { + throw new ArgumentException("Requirement must be Any when there are multiple variants.", nameof(Requirement)); + } + enabled = await variants.Any( async variant => { - Variant assignedVariant = await _featureManager.GetVariantAsync(features.First()); + Variant assignedVariant = await _featureManager.GetVariantAsync(features.First()).ConfigureAwait(false); return variant == assignedVariant?.Name; }); From b207a403db6fb6041c4e2c1d48adfda6bcbe8400 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Fri, 10 May 2024 00:28:55 +0800 Subject: [PATCH 09/10] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9cde2423..651e6dff 100644 --- a/README.md +++ b/README.md @@ -376,7 +376,7 @@ The `` tag can reference multiple features/variants by specifying a com **Note:** if `variant` is specified, only *one* feature should be specified. -By default, all listed features must be enabled for the feature tag to be rendered. This behavior can be overidden by adding the `requirement` attribute as seen in the example below. Note that the `requirement` attribute will have no effect on `variant`. +By default, all listed features must be enabled for the feature tag to be rendered. This behavior can be overidden by adding the `requirement` attribute as seen in the example below. ``` HTML+Razor From 87cb561599a9213dd375d4c12b7623633207a7e3 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Fri, 17 May 2024 13:48:21 +0800 Subject: [PATCH 10/10] update --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dba97bfa..5e71c119 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ public interface IDisabledFeaturesHandler ### View -In MVC views `` tags can be used to conditionally render content based on whether a feature is enabled or whether specific variant of a feature is assigned. For more information about variant, please refer to the [variants](./README.md#Variants) section. +In MVC views `` tags can be used to conditionally render content based on whether a feature is enabled or whether specific variant of a feature is assigned. For more information about variants, please refer to the [variants](./README.md#Variants) section. ``` HTML+Razor @@ -276,7 +276,7 @@ You can also negate the tag helper evaluation to display content when a feature ``` -The `` tag can reference multiple features/variants by specifying a comma separated list of features/variants in the `name`/`variant` attribute. Note that if `variant` is specified, only *one* feature should be specified. +The `` tag can reference multiple features/variants by specifying a comma separated list of features/variants in the `name`/`variant` attribute. ``` HTML+Razor @@ -292,7 +292,9 @@ The `` tag can reference multiple features/variants by specifying a com **Note:** if `variant` is specified, only *one* feature should be specified. -By default, all listed features must be enabled for the feature tag to be rendered. This behavior can be overidden by adding the `requirement` attribute as seen in the example below. +By default, all listed features must be enabled for the feature tag to be rendered. This behavior can be overridden by adding the `requirement` attribute as seen in the example below. + +**Note:** If a `requirement` of `And` is used in conjunction with `variant` an error will be thrown, as multiple variants can never be assigned. ``` HTML+Razor @@ -301,6 +303,7 @@ By default, all listed features must be enabled for the feature tag to be render ``` The `` tag requires a tag helper to work. This can be done by adding the feature management tag helper to the _ViewImports.cshtml_ file. + ``` HTML+Razor @addTagHelper *, Microsoft.FeatureManagement.AspNetCore ```