-
Notifications
You must be signed in to change notification settings - Fork 127
Expand file tree
/
Copy pathConfigurationFeatureDefinitionProvider.cs
More file actions
225 lines (193 loc) · 8.04 KB
/
ConfigurationFeatureDefinitionProvider.cs
File metadata and controls
225 lines (193 loc) · 8.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.FeatureManagement.Comparers;
namespace Microsoft.FeatureManagement
{
/// <summary>
/// A feature definition provider that pulls feature definitions from the .NET Core <see cref="IConfiguration"/> system.
/// </summary>
sealed class ConfigurationFeatureDefinitionProvider : IFeatureDefinitionProvider, IDisposable
{
private const string FeatureFiltersSectionName = "EnabledFor";
private const string DefaultDefinitionSectionName = "Default";
private readonly IConfiguration _configuration;
private readonly ConcurrentDictionary<string, FeatureDefinition> _definitions;
private IDisposable _changeSubscription;
private int _stale = 0;
public ConfigurationFeatureDefinitionProvider(IConfiguration configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_definitions = new ConcurrentDictionary<string, FeatureDefinition>();
_changeSubscription = ChangeToken.OnChange(
() => _configuration.GetReloadToken(),
() => _stale = 1);
}
public void Dispose()
{
_changeSubscription?.Dispose();
_changeSubscription = null;
}
public Task<FeatureDefinition> GetFeatureDefinitionAsync(string featureName)
{
if (featureName == null)
{
throw new ArgumentNullException(nameof(featureName));
}
if (Interlocked.Exchange(ref _stale, 0) != 0)
{
_definitions.Clear();
}
//
// Query by feature name
FeatureDefinition definition = _definitions.GetOrAdd(featureName, (name) => ReadFeatureDefinition(name));
FeatureDefinition defaultDefinition =
_definitions.GetOrAdd(DefaultDefinitionSectionName, (name) => ReadFeatureDefinition(name));
definition = MergeDefinitions(definition, defaultDefinition);
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<FeatureDefinition> GetAllFeatureDefinitionsAsync()
#pragma warning restore CS1998
{
if (Interlocked.Exchange(ref _stale, 0) != 0)
{
_definitions.Clear();
}
//
// Iterate over all features registered in the system at initial invocation time
foreach (IConfigurationSection featureSection in GetFeatureDefinitionSections())
{
//
// Underlying IConfigurationSection data is dynamic so latest feature definitions are returned
yield return _definitions.GetOrAdd(featureSection.Key, (_) => ReadFeatureDefinition(featureSection));
}
}
private FeatureDefinition ReadFeatureDefinition(string featureName)
{
IConfigurationSection configuration = GetFeatureDefinitionSections()
.FirstOrDefault(section => section.Key.Equals(featureName, StringComparison.OrdinalIgnoreCase));
if (configuration == null)
{
return null;
}
return ReadFeatureDefinition(configuration);
}
private FeatureDefinition ReadFeatureDefinition(IConfigurationSection configurationSection)
{
/*
We support
myFeature: {
enabledFor: [ "myFeatureFilter1", "myFeatureFilter2" ]
},
myDisabledFeature: {
enabledFor: [ ]
},
myFeature2: {
enabledFor: "myFeatureFilter1;myFeatureFilter2"
},
myDisabledFeature2: {
enabledFor: ""
},
myFeature3: "myFeatureFilter1;myFeatureFilter2",
myDisabledFeature3: "",
myAlwaysEnabledFeature: true,
myAlwaysDisabledFeature: false // removing this line would be the same as setting it to false
myAlwaysEnabledFeature2: {
enabledFor: true
},
myAlwaysDisabledFeature2: {
enabledFor: false
}
*/
var enabledFor = new List<FeatureFilterConfiguration>();
string val = configurationSection.Value; // configuration[$"{featureName}"];
if (string.IsNullOrEmpty(val))
{
val = configurationSection[FeatureFiltersSectionName];
}
if (!string.IsNullOrEmpty(val) && bool.TryParse(val, out bool result) && result)
{
//
//myAlwaysEnabledFeature: true
// OR
//myAlwaysEnabledFeature: {
// enabledFor: true
//}
enabledFor.Add(new FeatureFilterConfiguration
{
Name = "AlwaysOn"
});
}
else
{
IEnumerable<IConfigurationSection> filterSections =
configurationSection.GetSection(FeatureFiltersSectionName).GetChildren();
foreach (IConfigurationSection section in filterSections)
{
//
// Arrays in json such as "myKey": [ "some", "values" ]
// Are accessed through the configuration system by using the array index as the property name, e.g. "myKey": { "0": "some", "1": "values" }
if (int.TryParse(section.Key, out int i) &&
!string.IsNullOrEmpty(section[nameof(FeatureFilterConfiguration.Name)]))
{
enabledFor.Add(new FeatureFilterConfiguration()
{
Name = section[nameof(FeatureFilterConfiguration.Name)],
Parameters = section.GetSection(nameof(FeatureFilterConfiguration.Parameters))
});
}
}
}
return new FeatureDefinition()
{
Name = configurationSection.Key,
EnabledFor = enabledFor
};
}
private IEnumerable<IConfigurationSection> GetFeatureDefinitionSections()
{
const string FeatureManagementSectionName = "FeatureManagement";
if (_configuration.GetChildren().Any(s =>
s.Key.Equals(FeatureManagementSectionName, StringComparison.OrdinalIgnoreCase)))
{
//
// Look for feature definitions under the "FeatureManagement" section
return _configuration.GetSection(FeatureManagementSectionName).GetChildren();
}
else
{
return _configuration.GetChildren();
}
}
private FeatureDefinition MergeDefinitions(FeatureDefinition targetDefinition,
FeatureDefinition otherDefinition)
{
if (targetDefinition is null)
{
return null;
}
if (otherDefinition is null)
{
return targetDefinition;
}
return new FeatureDefinition
{
Name = targetDefinition.Name,
EnabledFor = targetDefinition.EnabledFor.Union(otherDefinition.EnabledFor,
new CompareByNameFeatureFilterConfigurationComparer())
};
}
}
}