Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/CustomAssignmentConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ public static async Task Main(string[] args)
// Get the feature manager from application services
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
IFeatureVariantManager variantManager = serviceProvider.GetRequiredService<IFeatureVariantManager>();
IDynamicFeatureManager dynamicFeatureManager = serviceProvider.GetRequiredService<IDynamicFeatureManager>();

DailyDiscountOptions discountOptions = await variantManager
DailyDiscountOptions discountOptions = await dynamicFeatureManager
.GetVariantAsync<DailyDiscountOptions>("DailyDiscount", CancellationToken.None);

//
Expand Down
2 changes: 1 addition & 1 deletion examples/CustomAssignmentConsoleApp/RecurringAssigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class RecurringAssigner : IFeatureVariantAssigner
{
public ValueTask<FeatureVariant> AssignVariantAsync(FeatureVariantAssignmentContext variantAssignmentContext, CancellationToken _)
{
FeatureDefinition featureDefinition = variantAssignmentContext.FeatureDefinition;
DynamicFeatureDefinition featureDefinition = variantAssignmentContext.FeatureDefinition;

FeatureVariant chosenVariant = null;

Expand Down
1 change: 1 addition & 0 deletions examples/FeatureFlagDemo/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.Assigners;
using Microsoft.FeatureManagement.FeatureFilters;

namespace FeatureFlagDemo
Expand Down
4 changes: 2 additions & 2 deletions examples/FeatureFlagDemo/Views/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@using Microsoft.FeatureManagement
@inject IFeatureVariantManager variantManager;
@inject IDynamicFeatureManager dynamicFeatureManager;
@{
DiscountBannerOptions opts = await variantManager.GetVariantAsync<DiscountBannerOptions>("DiscountBanner", Context.RequestAborted);
DiscountBannerOptions opts = await dynamicFeatureManager.GetVariantAsync<DiscountBannerOptions>("DiscountBanner", Context.RequestAborted);
}

<!DOCTYPE html>
Expand Down
170 changes: 86 additions & 84 deletions examples/FeatureFlagDemo/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,100 +5,102 @@
}
},
"AllowedHosts": "*",

// Define feature flags in config file
"FeatureManagement": {

"Home": true,
"Beta": {
"EnabledFor": [
{
"Name": "Targeting",
"Parameters": { // This json object maps to a strongly typed configuration class
"Audience": {
"Users": [
"Jeff",
"Alicia"
],
"Groups": [
{
"Name": "Ring0",
"RolloutPercentage": 80
},
{
"Name": "Ring1",
"RolloutPercentage": 50
}
],
"DefaultRolloutPercentage": 20
"FeatureFlags": {
"Home": true,
"Beta": {
"EnabledFor": [
{
"Name": "Targeting",
"Parameters": { // This json object maps to a strongly typed configuration class
"Audience": {
"Users": [
"Jeff",
"Alicia"
],
"Groups": [
{
"Name": "Ring0",
"RolloutPercentage": 80
},
{
"Name": "Ring1",
"RolloutPercentage": 50
}
],
"DefaultRolloutPercentage": 20
}
}
}
}
]
},
"CustomViewData": {
"EnabledFor": [
{
"Name": "Browser",
"Parameters": {
"AllowedBrowsers": [ "Chrome", "Edge" ]
]
},
"CustomViewData": {
"EnabledFor": [
{
"Name": "Browser",
"Parameters": {
"AllowedBrowsers": [ "Chrome", "Edge" ]
}
}
}
]
},
"ContentEnhancement": {
"EnabledFor": [
{
"Name": "TimeWindow",
"Parameters": {
"Start": "Wed, 01 May 2019 13:59:59 GMT",
"End": "Mon, 01 July 2019 00:00:00 GMT"
]
},
"ContentEnhancement": {
"EnabledFor": [
{
"Name": "TimeWindow",
"Parameters": {
"Start": "Wed, 01 May 2019 13:59:59 GMT",
"End": "Mon, 01 July 2019 00:00:00 GMT"
}
}
}
]
},
"EnhancedPipeline": {
"EnabledFor": [
{
"Name": "Microsoft.Percentage",
"Parameters": {
"Value": 50
]
},
"EnhancedPipeline": {
"EnabledFor": [
{
"Name": "Microsoft.Percentage",
"Parameters": {
"Value": 50
}
}
}
]
]
}
},
"DiscountBanner": {
"Assigner": "Targeting",
"Variants": [
{
"Default": true,
"Name": "Big",
"ConfigurationReference": "DiscountBanner:Big"
},
{
"Name": "Small",
"ConfigurationReference": "DiscountBanner:Small",
"AssignmentParameters": {
"Audience": {
"Users": [
"Jeff",
"Alicia"
],
"Groups": [
{
"Name": "Ring0",
"RolloutPercentage": 80
},
{
"Name": "Ring1",
"RolloutPercentage": 50
}
],
"DefaultRolloutPercentage": 20
"DynamicFeatures": {
"DiscountBanner": {
"Assigner": "Targeting",
"Variants": [
{
"Default": true,
"Name": "Big",
"ConfigurationReference": "DiscountBanner:Big"
},
{
"Name": "Small",
"ConfigurationReference": "DiscountBanner:Small",
"AssignmentParameters": {
"Audience": {
"Users": [
"Jeff",
"Alicia"
],
"Groups": [
{
"Name": "Ring0",
"RolloutPercentage": 80
},
{
"Name": "Ring1",
"RolloutPercentage": 50
}
],
"DefaultRolloutPercentage": 20
}
}
}
}
]
]
}
}
},
"DiscountBanner": {
Expand Down
5 changes: 3 additions & 2 deletions examples/TargetingConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.Assigners;
using Microsoft.FeatureManagement.FeatureFilters;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -40,7 +41,7 @@ public static async Task Main(string[] args)
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
IFeatureManager featureManager = serviceProvider.GetRequiredService<IFeatureManager>();
IFeatureVariantManager variantManager = serviceProvider.GetRequiredService<IFeatureVariantManager>();
IDynamicFeatureManager dynamicFeatureManager = serviceProvider.GetRequiredService<IDynamicFeatureManager>();

//
// We'll simulate a task to run on behalf of each known user
Expand Down Expand Up @@ -76,7 +77,7 @@ public static async Task Main(string[] args)

//
// Retrieve feature variant using targeting
CartOptions cartOptions = await variantManager
CartOptions cartOptions = await dynamicFeatureManager
.GetVariantAsync<CartOptions, TargetingContext>(
DynamicFeatureName,
targetingContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.FeatureManagement
{
/// <summary>
/// A feature definition provider that pulls dynamic feature definitions from the .NET Core <see cref="IConfiguration"/> system.
/// </summary>
sealed class ConfigurationDynamicFeatureDefinitionProvider : IDynamicFeatureDefinitionProvider, IDisposable
{
private const string FeatureManagementSectionName = "FeatureManagement";
private const string DynamicFeatureDefinitionsSectionName= "DynamicFeatures";
private const string FeatureVariantsSectionName = "Variants";
private readonly IConfiguration _configuration;
private readonly ConcurrentDictionary<string, DynamicFeatureDefinition> _dynamicFeatureDefinitions;
private IDisposable _changeSubscription;
private int _stale = 0;

public ConfigurationDynamicFeatureDefinitionProvider(IConfiguration configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_dynamicFeatureDefinitions = new ConcurrentDictionary<string, DynamicFeatureDefinition>();

_changeSubscription = ChangeToken.OnChange(
() => _configuration.GetReloadToken(),
() => _stale = 1);
}

public void Dispose()
{
_changeSubscription?.Dispose();

_changeSubscription = null;
}

public Task<DynamicFeatureDefinition> GetDynamicFeatureDefinitionAsync(string featureName, CancellationToken cancellationToken = default)
{
if (featureName == null)
{
throw new ArgumentNullException(nameof(featureName));
}

EnsureFresh();

//
// Query by feature name
DynamicFeatureDefinition definition = _dynamicFeatureDefinitions.GetOrAdd(featureName, (name) => ReadDynamicFeatureDefinition(name));

return Task.FromResult(definition);
}

//
// 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<DynamicFeatureDefinition> GetAllDynamicFeatureDefinitionsAsync([EnumeratorCancellation] CancellationToken cancellationToken)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
EnsureFresh();

//
// Iterate over all features registered in the system at initial invocation time
foreach (IConfigurationSection featureSection in GetDynamicFeatureDefinitionSections())
{
//
// Underlying IConfigurationSection data is dynamic so latest feature definitions are returned
yield return _dynamicFeatureDefinitions.GetOrAdd(featureSection.Key, (_) => ReadDynamicFeatureDefinition(featureSection));
}
}

private DynamicFeatureDefinition ReadDynamicFeatureDefinition(string featureName)
{
IConfigurationSection configuration = GetDynamicFeatureDefinitionSections()
.FirstOrDefault(section => section.Key.Equals(featureName, StringComparison.OrdinalIgnoreCase));

if (configuration == null)
{
return null;
}

return ReadDynamicFeatureDefinition(configuration);
}

private DynamicFeatureDefinition ReadDynamicFeatureDefinition(IConfigurationSection configurationSection)
{
Debug.Assert(configurationSection != null);

var variants = new List<FeatureVariant>();

foreach (IConfigurationSection section in configurationSection.GetSection(FeatureVariantsSectionName).GetChildren())
{
if (int.TryParse(section.Key, out int _) && !string.IsNullOrEmpty(section[nameof(FeatureVariant.Name)]))
{
variants.Add(new FeatureVariant
{
Default = section.GetValue<bool>(nameof(FeatureVariant.Default)),
Name = section.GetValue<string>(nameof(FeatureVariant.Name)),
ConfigurationReference = section.GetValue<string>(nameof(FeatureVariant.ConfigurationReference)),
AssignmentParameters = section.GetSection(nameof(FeatureVariant.AssignmentParameters))
});
}
}

return new DynamicFeatureDefinition()
{
Name = configurationSection.Key,
Variants = variants,
Assigner = configurationSection.GetValue<string>(nameof(DynamicFeatureDefinition.Assigner))
};
}

private IEnumerable<IConfigurationSection> GetDynamicFeatureDefinitionSections()
{
//
// Look for feature definitions under the "FeatureManagement" section
IConfiguration featureManagementSection = _configuration.GetChildren().Any(s => s.Key.Equals(FeatureManagementSectionName, StringComparison.OrdinalIgnoreCase)) ?
_configuration.GetSection(FeatureManagementSectionName) :
_configuration;

return featureManagementSection
.GetSection(DynamicFeatureDefinitionsSectionName)
.GetChildren();
}

private void EnsureFresh()
{
if (Interlocked.Exchange(ref _stale, 0) != 0)
{
_dynamicFeatureDefinitions.Clear();
}
}
}
}
Loading