diff --git a/README.md b/README.md index bad85412..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 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. For more information about variants, please refer to the [variants](./README.md#Variants) section. ``` HTML+Razor @@ -256,7 +256,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 @@ -264,7 +270,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. ``` HTML+Razor @@ -272,7 +284,17 @@ 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 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 @@ -281,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 ``` diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs index 4a0da1e0..3eff1ad5 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. // using Microsoft.AspNetCore.Razor.TagHelpers; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -13,7 +14,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 +31,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, must contain only one feature name and will have no effect. + /// + 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,11 +59,35 @@ 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()); + + 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)); + } + 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()); + + 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()).ConfigureAwait(false); - 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)); + return variant == assignedVariant?.Name; + }); + } } if (Negate)