Skip to content

Commit 08d38af

Browse files
committed
* Separated feature flag implementation from dynamic feature impelementation in FeatureManager, ConfigurationFeatureDefinitionProvider, and FeatureManagerSnapshot..
* Separated IConfigurationDefinitionProvider into IFeatureFlagDefinitionProvider and IDynamicFeatureDefinitionProvider * Added missing default cancellation tokens.
1 parent c19e457 commit 08d38af

20 files changed

+598
-448
lines changed

examples/CustomAssignmentConsoleApp/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ public static async Task Main(string[] args)
3535
// Get the feature manager from application services
3636
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
3737
{
38-
IDynamicFeatureManager variantManager = serviceProvider.GetRequiredService<IDynamicFeatureManager>();
38+
IDynamicFeatureManager dynamicFeatureManager = serviceProvider.GetRequiredService<IDynamicFeatureManager>();
3939

40-
DailyDiscountOptions discountOptions = await variantManager
40+
DailyDiscountOptions discountOptions = await dynamicFeatureManager
4141
.GetVariantAsync<DailyDiscountOptions>("DailyDiscount", CancellationToken.None);
4242

4343
//

examples/FeatureFlagDemo/Views/Shared/_Layout.cshtml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@using Microsoft.FeatureManagement
2-
@inject IDynamicFeatureManager variantManager;
2+
@inject IDynamicFeatureManager dynamicFeatureManager;
33
@{
4-
DiscountBannerOptions opts = await variantManager.GetVariantAsync<DiscountBannerOptions>("DiscountBanner", Context.RequestAborted);
4+
DiscountBannerOptions opts = await dynamicFeatureManager.GetVariantAsync<DiscountBannerOptions>("DiscountBanner", Context.RequestAborted);
55
}
66

77
<!DOCTYPE html>

examples/TargetingConsoleApp/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public static async Task Main(string[] args)
4141
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
4242
{
4343
IFeatureManager featureManager = serviceProvider.GetRequiredService<IFeatureManager>();
44-
IDynamicFeatureManager variantManager = serviceProvider.GetRequiredService<IDynamicFeatureManager>();
44+
IDynamicFeatureManager dynamicFeatureManager = serviceProvider.GetRequiredService<IDynamicFeatureManager>();
4545

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

7878
//
7979
// Retrieve feature variant using targeting
80-
CartOptions cartOptions = await variantManager
80+
CartOptions cartOptions = await dynamicFeatureManager
8181
.GetVariantAsync<CartOptions, TargetingContext>(
8282
DynamicFeatureName,
8383
targetingContext,
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.Primitives;
6+
using System;
7+
using System.Collections.Concurrent;
8+
using System.Collections.Generic;
9+
using System.Diagnostics;
10+
using System.Linq;
11+
using System.Runtime.CompilerServices;
12+
using System.Threading;
13+
using System.Threading.Tasks;
14+
15+
namespace Microsoft.FeatureManagement
16+
{
17+
/// <summary>
18+
/// A feature definition provider that pulls dynamic feature definitions from the .NET Core <see cref="IConfiguration"/> system.
19+
/// </summary>
20+
sealed class ConfigurationDynamicFeatureDefinitionProvider : IDynamicFeatureDefinitionProvider, IDisposable
21+
{
22+
private const string FeatureManagementSectionName = "FeatureManagement";
23+
private const string DynamicFeatureDefinitionsSectionName= "DynamicFeatures";
24+
private const string FeatureVariantsSectionName = "Variants";
25+
private readonly IConfiguration _configuration;
26+
private readonly ConcurrentDictionary<string, DynamicFeatureDefinition> _dynamicFeatureDefinitions;
27+
private IDisposable _changeSubscription;
28+
private int _stale = 0;
29+
30+
public ConfigurationDynamicFeatureDefinitionProvider(IConfiguration configuration)
31+
{
32+
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
33+
_dynamicFeatureDefinitions = new ConcurrentDictionary<string, DynamicFeatureDefinition>();
34+
35+
_changeSubscription = ChangeToken.OnChange(
36+
() => _configuration.GetReloadToken(),
37+
() => _stale = 1);
38+
}
39+
40+
public void Dispose()
41+
{
42+
_changeSubscription?.Dispose();
43+
44+
_changeSubscription = null;
45+
}
46+
47+
public Task<DynamicFeatureDefinition> GetDynamicFeatureDefinitionAsync(string featureName, CancellationToken cancellationToken = default)
48+
{
49+
if (featureName == null)
50+
{
51+
throw new ArgumentNullException(nameof(featureName));
52+
}
53+
54+
EnsureFresh();
55+
56+
//
57+
// Query by feature name
58+
DynamicFeatureDefinition definition = _dynamicFeatureDefinitions.GetOrAdd(featureName, (name) => ReadDynamicFeatureDefinition(name));
59+
60+
return Task.FromResult(definition);
61+
}
62+
63+
//
64+
// The async key word is necessary for creating IAsyncEnumerable.
65+
// The need to disable this warning occurs when implementaing async stream synchronously.
66+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
67+
public async IAsyncEnumerable<DynamicFeatureDefinition> GetAllDynamicFeatureDefinitionsAsync([EnumeratorCancellation] CancellationToken cancellationToken)
68+
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
69+
{
70+
EnsureFresh();
71+
72+
//
73+
// Iterate over all features registered in the system at initial invocation time
74+
foreach (IConfigurationSection featureSection in GetDynamicFeatureDefinitionSections())
75+
{
76+
//
77+
// Underlying IConfigurationSection data is dynamic so latest feature definitions are returned
78+
yield return _dynamicFeatureDefinitions.GetOrAdd(featureSection.Key, (_) => ReadDynamicFeatureDefinition(featureSection));
79+
}
80+
}
81+
82+
private DynamicFeatureDefinition ReadDynamicFeatureDefinition(string featureName)
83+
{
84+
IConfigurationSection configuration = GetDynamicFeatureDefinitionSections()
85+
.FirstOrDefault(section => section.Key.Equals(featureName, StringComparison.OrdinalIgnoreCase));
86+
87+
if (configuration == null)
88+
{
89+
return null;
90+
}
91+
92+
return ReadDynamicFeatureDefinition(configuration);
93+
}
94+
95+
private DynamicFeatureDefinition ReadDynamicFeatureDefinition(IConfigurationSection configurationSection)
96+
{
97+
Debug.Assert(configurationSection != null);
98+
99+
var variants = new List<FeatureVariant>();
100+
101+
foreach (IConfigurationSection section in configurationSection.GetSection(FeatureVariantsSectionName).GetChildren())
102+
{
103+
if (int.TryParse(section.Key, out int _) && !string.IsNullOrEmpty(section[nameof(FeatureVariant.Name)]))
104+
{
105+
variants.Add(new FeatureVariant
106+
{
107+
Default = section.GetValue<bool>(nameof(FeatureVariant.Default)),
108+
Name = section.GetValue<string>(nameof(FeatureVariant.Name)),
109+
ConfigurationReference = section.GetValue<string>(nameof(FeatureVariant.ConfigurationReference)),
110+
AssignmentParameters = section.GetSection(nameof(FeatureVariant.AssignmentParameters))
111+
});
112+
}
113+
}
114+
115+
return new DynamicFeatureDefinition()
116+
{
117+
Name = configurationSection.Key,
118+
Variants = variants,
119+
Assigner = configurationSection.GetValue<string>(nameof(DynamicFeatureDefinition.Assigner))
120+
};
121+
}
122+
123+
private IEnumerable<IConfigurationSection> GetDynamicFeatureDefinitionSections()
124+
{
125+
//
126+
// Look for feature definitions under the "FeatureManagement" section
127+
IConfiguration featureManagementSection = _configuration.GetChildren().Any(s => s.Key.Equals(FeatureManagementSectionName, StringComparison.OrdinalIgnoreCase)) ?
128+
_configuration.GetSection(FeatureManagementSectionName) :
129+
_configuration;
130+
131+
return featureManagementSection
132+
.GetSection(DynamicFeatureDefinitionsSectionName)
133+
.GetChildren();
134+
}
135+
136+
private void EnsureFresh()
137+
{
138+
if (Interlocked.Exchange(ref _stale, 0) != 0)
139+
{
140+
_dynamicFeatureDefinitions.Clear();
141+
}
142+
}
143+
}
144+
}

src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs renamed to src/Microsoft.FeatureManagement/ConfigurationFeatureFlagDefinitionProvider.cs

Lines changed: 10 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,22 @@
1515
namespace Microsoft.FeatureManagement
1616
{
1717
/// <summary>
18-
/// A feature definition provider that pulls feature definitions from the .NET Core <see cref="IConfiguration"/> system.
18+
/// A feature definition provider that pulls feature flag definitions from the .NET Core <see cref="IConfiguration"/> system.
1919
/// </summary>
20-
sealed class ConfigurationFeatureDefinitionProvider : IFeatureDefinitionProvider, IDisposable
20+
sealed class ConfigurationFeatureFlagDefinitionProvider : IFeatureFlagDefinitionProvider, IDisposable
2121
{
2222
private const string FeatureManagementSectionName = "FeatureManagement";
2323
private const string FeatureFlagDefinitionsSectionName = "FeatureFlags";
24-
private const string DynamicFeatureDefinitionsSectionName= "DynamicFeatures";
2524
private const string FeatureFiltersSectionName = "EnabledFor";
26-
private const string FeatureVariantsSectionName = "Variants";
2725
private readonly IConfiguration _configuration;
2826
private readonly ConcurrentDictionary<string, FeatureFlagDefinition> _featureFlagDefinitions;
29-
private readonly ConcurrentDictionary<string, DynamicFeatureDefinition> _dynamicFeatureDefinitions;
3027
private IDisposable _changeSubscription;
3128
private int _stale = 0;
3229

33-
public ConfigurationFeatureDefinitionProvider(IConfiguration configuration)
30+
public ConfigurationFeatureFlagDefinitionProvider(IConfiguration configuration)
3431
{
3532
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
3633
_featureFlagDefinitions = new ConcurrentDictionary<string, FeatureFlagDefinition>();
37-
_dynamicFeatureDefinitions = new ConcurrentDictionary<string, DynamicFeatureDefinition>();
3834

3935
_changeSubscription = ChangeToken.OnChange(
4036
() => _configuration.GetReloadToken(),
@@ -83,41 +79,6 @@ public async IAsyncEnumerable<FeatureFlagDefinition> GetAllFeatureFlagDefinition
8379
}
8480
}
8581

86-
public Task<DynamicFeatureDefinition> GetDynamicFeatureDefinitionAsync(string featureName, CancellationToken cancellationToken = default)
87-
{
88-
if (featureName == null)
89-
{
90-
throw new ArgumentNullException(nameof(featureName));
91-
}
92-
93-
EnsureFresh();
94-
95-
//
96-
// Query by feature name
97-
DynamicFeatureDefinition definition = _dynamicFeatureDefinitions.GetOrAdd(featureName, (name) => ReadDynamicFeatureDefinition(name));
98-
99-
return Task.FromResult(definition);
100-
}
101-
102-
//
103-
// The async key word is necessary for creating IAsyncEnumerable.
104-
// The need to disable this warning occurs when implementaing async stream synchronously.
105-
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
106-
public async IAsyncEnumerable<DynamicFeatureDefinition> GetAllDynamicFeatureDefinitionsAsync([EnumeratorCancellation] CancellationToken cancellationToken)
107-
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
108-
{
109-
EnsureFresh();
110-
111-
//
112-
// Iterate over all features registered in the system at initial invocation time
113-
foreach (IConfigurationSection featureSection in GetDynamicFeatureDefinitionSections())
114-
{
115-
//
116-
// Underlying IConfigurationSection data is dynamic so latest feature definitions are returned
117-
yield return _dynamicFeatureDefinitions.GetOrAdd(featureSection.Key, (_) => ReadDynamicFeatureDefinition(featureSection));
118-
}
119-
}
120-
12182
private FeatureFlagDefinition ReadFeatureFlagDefinition(string featureName)
12283
{
12384
IConfigurationSection configuration = GetFeatureFlagDefinitionSections()
@@ -131,19 +92,6 @@ private FeatureFlagDefinition ReadFeatureFlagDefinition(string featureName)
13192
return ReadFeatureFlagDefinition(configuration);
13293
}
13394

134-
private DynamicFeatureDefinition ReadDynamicFeatureDefinition(string featureName)
135-
{
136-
IConfigurationSection configuration = GetDynamicFeatureDefinitionSections()
137-
.FirstOrDefault(section => section.Key.Equals(featureName, StringComparison.OrdinalIgnoreCase));
138-
139-
if (configuration == null)
140-
{
141-
return null;
142-
}
143-
144-
return ReadDynamicFeatureDefinition(configuration);
145-
}
146-
14795
private FeatureFlagDefinition ReadFeatureFlagDefinition(IConfigurationSection configurationSection)
14896
{
14997
/*
@@ -226,37 +174,15 @@ We support
226174
};
227175
}
228176

229-
private DynamicFeatureDefinition ReadDynamicFeatureDefinition(IConfigurationSection configurationSection)
230-
{
231-
Debug.Assert(configurationSection != null);
232-
233-
var variants = new List<FeatureVariant>();
234-
235-
foreach (IConfigurationSection section in configurationSection.GetSection(FeatureVariantsSectionName).GetChildren())
236-
{
237-
if (int.TryParse(section.Key, out int _) && !string.IsNullOrEmpty(section[nameof(FeatureVariant.Name)]))
238-
{
239-
variants.Add(new FeatureVariant
240-
{
241-
Default = section.GetValue<bool>(nameof(FeatureVariant.Default)),
242-
Name = section.GetValue<string>(nameof(FeatureVariant.Name)),
243-
ConfigurationReference = section.GetValue<string>(nameof(FeatureVariant.ConfigurationReference)),
244-
AssignmentParameters = section.GetSection(nameof(FeatureVariant.AssignmentParameters))
245-
});
246-
}
247-
}
248-
249-
return new DynamicFeatureDefinition()
250-
{
251-
Name = configurationSection.Key,
252-
Variants = variants,
253-
Assigner = configurationSection.GetValue<string>(nameof(DynamicFeatureDefinition.Assigner))
254-
};
255-
}
256-
257177
private IEnumerable<IConfigurationSection> GetFeatureFlagDefinitionSections()
258178
{
259-
IEnumerable<IConfigurationSection> featureManagementChildren = GetFeatureManagementSection().GetChildren();
179+
//
180+
// Look for feature definitions under the "FeatureManagement" section
181+
IConfiguration featureManagementSection = _configuration.GetChildren().Any(s => s.Key.Equals(FeatureManagementSectionName, StringComparison.OrdinalIgnoreCase)) ?
182+
_configuration.GetSection(FeatureManagementSectionName) :
183+
_configuration;
184+
185+
IEnumerable<IConfigurationSection> featureManagementChildren = featureManagementSection.GetChildren();
260186

261187
IConfigurationSection featureFlagsSection = featureManagementChildren.FirstOrDefault(s => s.Key == FeatureFlagDefinitionsSectionName);
262188

@@ -266,29 +192,12 @@ private IEnumerable<IConfigurationSection> GetFeatureFlagDefinitionSections()
266192
featureManagementChildren :
267193
featureFlagsSection.GetChildren();
268194
}
269-
270-
private IEnumerable<IConfigurationSection> GetDynamicFeatureDefinitionSections()
271-
{
272-
return GetFeatureManagementSection()
273-
.GetSection(DynamicFeatureDefinitionsSectionName)
274-
.GetChildren();
275-
}
276-
277-
private IConfiguration GetFeatureManagementSection()
278-
{
279-
//
280-
// Look for feature definitions under the "FeatureManagement" section
281-
return _configuration.GetChildren().Any(s => s.Key.Equals(FeatureManagementSectionName, StringComparison.OrdinalIgnoreCase)) ?
282-
_configuration.GetSection(FeatureManagementSectionName) :
283-
_configuration;
284-
}
285195

286196
private void EnsureFresh()
287197
{
288198
if (Interlocked.Exchange(ref _stale, 0) != 0)
289199
{
290200
_featureFlagDefinitions.Clear();
291-
_dynamicFeatureDefinitions.Clear();
292201
}
293202
}
294203
}

0 commit comments

Comments
 (0)