Skip to content

Commit 7b0fb67

Browse files
Merge pull request #622 from Azure/main
Merge main to release/stable/v8
2 parents 0afb56a + 662250e commit 7b0fb67

32 files changed

+963
-563
lines changed

src/Microsoft.Azure.AppConfiguration.AspNetCore/Microsoft.Azure.AppConfiguration.AspNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<!-- Nuget Package Version Settings -->
2222

2323
<PropertyGroup>
24-
<OfficialVersion>8.0.0</OfficialVersion>
24+
<OfficialVersion>8.1.0</OfficialVersion>
2525
</PropertyGroup>
2626

2727
<PropertyGroup Condition="'$(CDP_PATCH_NUMBER)'!='' AND '$(CDP_BUILD_TYPE)'=='Official'">

src/Microsoft.Azure.AppConfiguration.Functions.Worker/Microsoft.Azure.AppConfiguration.Functions.Worker.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<!-- Nuget Package Version Settings -->
2525

2626
<PropertyGroup>
27-
<OfficialVersion>8.0.0</OfficialVersion>
27+
<OfficialVersion>8.1.0</OfficialVersion>
2828
</PropertyGroup>
2929

3030
<PropertyGroup Condition="'$(CDP_PATCH_NUMBER)'!='' AND '$(CDP_BUILD_TYPE)'=='Official'">

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationExtensions.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ private static bool IsProviderDisabled()
3333
/// </summary>
3434
/// <param name="configurationBuilder">The configuration builder to add key-values to.</param>
3535
/// <param name="connectionString">The connection string used to connect to the configuration store.</param>
36-
/// <param name="optional">Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration.</param>
36+
/// <param name="optional">Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration.
37+
/// <exception cref="ArgumentException"/> will always be thrown when the caller gives an invalid input configuration (connection strings, endpoints, key/label filters...etc).
38+
/// </param>
3739
/// <returns>The provided configuration builder.</returns>
3840
public static IConfigurationBuilder AddAzureAppConfiguration(
3941
this IConfigurationBuilder configurationBuilder,
@@ -48,7 +50,9 @@ public static IConfigurationBuilder AddAzureAppConfiguration(
4850
/// </summary>
4951
/// <param name="configurationBuilder">The configuration builder to add key-values to.</param>
5052
/// <param name="connectionStrings">The list of connection strings used to connect to the configuration store and its replicas.</param>
51-
/// <param name="optional">Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration.</param>
53+
/// <param name="optional">Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration.
54+
/// <exception cref="ArgumentException"/> will always be thrown when the caller gives an invalid input configuration (connection strings, endpoints, key/label filters...etc).
55+
/// </param>
5256
/// <returns>The provided configuration builder.</returns>
5357
public static IConfigurationBuilder AddAzureAppConfiguration(
5458
this IConfigurationBuilder configurationBuilder,
@@ -63,7 +67,9 @@ public static IConfigurationBuilder AddAzureAppConfiguration(
6367
/// </summary>
6468
/// <param name="configurationBuilder">The configuration builder to add key-values to.</param>
6569
/// <param name="action">A callback used to configure Azure App Configuration options.</param>
66-
/// <param name="optional">Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration.</param>
70+
/// <param name="optional">Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration.
71+
/// <exception cref="ArgumentException"/> will always be thrown when the caller gives an invalid input configuration (connection strings, endpoints, key/label filters...etc).
72+
/// </param>
6773
/// <returns>The provided configuration builder.</returns>
6874
public static IConfigurationBuilder AddAzureAppConfiguration(
6975
this IConfigurationBuilder configurationBuilder,

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationOptions.cs

Lines changed: 83 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ public class AzureAppConfigurationOptions
2323
private const int MaxRetries = 2;
2424
private static readonly TimeSpan MaxRetryDelay = TimeSpan.FromMinutes(1);
2525

26-
private List<KeyValueWatcher> _changeWatchers = new List<KeyValueWatcher>();
27-
private List<KeyValueWatcher> _multiKeyWatchers = new List<KeyValueWatcher>();
26+
private List<KeyValueWatcher> _individualKvWatchers = new List<KeyValueWatcher>();
27+
private List<KeyValueWatcher> _ffWatchers = new List<KeyValueWatcher>();
2828
private List<IKeyValueAdapter> _adapters;
2929
private List<Func<ConfigurationSetting, ValueTask<ConfigurationSetting>>> _mappers = new List<Func<ConfigurationSetting, ValueTask<ConfigurationSetting>>>();
30-
private List<KeyValueSelector> _kvSelectors = new List<KeyValueSelector>();
30+
private List<KeyValueSelector> _selectors;
3131
private IConfigurationRefresher _refresher = new AzureAppConfigurationRefresher();
32+
private bool _selectCalled = false;
3233

3334
// The following set is sorted in descending order.
3435
// Since multiple prefixes could start with the same characters, we need to trim the longest prefix first.
@@ -62,19 +63,29 @@ public class AzureAppConfigurationOptions
6263
internal TokenCredential Credential { get; private set; }
6364

6465
/// <summary>
65-
/// A collection of <see cref="KeyValueSelector"/>.
66+
/// A collection of <see cref="KeyValueSelector"/> specified by user.
6667
/// </summary>
67-
internal IEnumerable<KeyValueSelector> KeyValueSelectors => _kvSelectors;
68+
internal IEnumerable<KeyValueSelector> Selectors => _selectors;
69+
70+
/// <summary>
71+
/// Indicates if <see cref="AzureAppConfigurationRefreshOptions.RegisterAll"/> was called.
72+
/// </summary>
73+
internal bool RegisterAllEnabled { get; private set; }
74+
75+
/// <summary>
76+
/// Refresh interval for selected key-value collections when <see cref="AzureAppConfigurationRefreshOptions.RegisterAll"/> is called.
77+
/// </summary>
78+
internal TimeSpan KvCollectionRefreshInterval { get; private set; }
6879

6980
/// <summary>
7081
/// A collection of <see cref="KeyValueWatcher"/>.
7182
/// </summary>
72-
internal IEnumerable<KeyValueWatcher> ChangeWatchers => _changeWatchers;
83+
internal IEnumerable<KeyValueWatcher> IndividualKvWatchers => _individualKvWatchers;
7384

7485
/// <summary>
7586
/// A collection of <see cref="KeyValueWatcher"/>.
7687
/// </summary>
77-
internal IEnumerable<KeyValueWatcher> MultiKeyWatchers => _multiKeyWatchers;
88+
internal IEnumerable<KeyValueWatcher> FeatureFlagWatchers => _ffWatchers;
7889

7990
/// <summary>
8091
/// A collection of <see cref="IKeyValueAdapter"/>.
@@ -96,11 +107,15 @@ internal IEnumerable<IKeyValueAdapter> Adapters
96107
internal IEnumerable<string> KeyPrefixes => _keyPrefixes;
97108

98109
/// <summary>
99-
/// An optional configuration client manager that can be used to provide clients to communicate with Azure App Configuration.
110+
/// For use in tests only. An optional configuration client manager that can be used to provide clients to communicate with Azure App Configuration.
100111
/// </summary>
101-
/// <remarks>This property is used only for unit testing.</remarks>
102112
internal IConfigurationClientManager ClientManager { get; set; }
103113

114+
/// <summary>
115+
/// For use in tests only. An optional class used to process pageable results from Azure App Configuration.
116+
/// </summary>
117+
internal IConfigurationSettingPageIterator ConfigurationSettingPageIterator { get; set; }
118+
104119
/// <summary>
105120
/// An optional timespan value to set the minimum backoff duration to a value other than the default.
106121
/// </summary>
@@ -142,6 +157,9 @@ public AzureAppConfigurationOptions()
142157
new JsonKeyValueAdapter(),
143158
new FeatureManagementKeyValueAdapter(FeatureFlagTracing)
144159
};
160+
161+
// Adds the default query to App Configuration if <see cref="Select"/> and <see cref="SelectSnapshot"/> are never called.
162+
_selectors = new List<KeyValueSelector> { new KeyValueSelector { KeyFilter = KeyFilter.Any, LabelFilter = LabelFilter.Null } };
145163
}
146164

147165
/// <summary>
@@ -170,22 +188,30 @@ public AzureAppConfigurationOptions Select(string keyFilter, string labelFilter
170188
throw new ArgumentNullException(nameof(keyFilter));
171189
}
172190

191+
// Do not support * and , for label filter for now.
192+
if (labelFilter != null && (labelFilter.Contains('*') || labelFilter.Contains(',')))
193+
{
194+
throw new ArgumentException("The characters '*' and ',' are not supported in label filters.", nameof(labelFilter));
195+
}
196+
173197
if (string.IsNullOrWhiteSpace(labelFilter))
174198
{
175199
labelFilter = LabelFilter.Null;
176200
}
177201

178-
// Do not support * and , for label filter for now.
179-
if (labelFilter.Contains('*') || labelFilter.Contains(','))
202+
if (!_selectCalled)
180203
{
181-
throw new ArgumentException("The characters '*' and ',' are not supported in label filters.", nameof(labelFilter));
204+
_selectors.Clear();
205+
206+
_selectCalled = true;
182207
}
183208

184-
_kvSelectors.AppendUnique(new KeyValueSelector
209+
_selectors.AppendUnique(new KeyValueSelector
185210
{
186211
KeyFilter = keyFilter,
187212
LabelFilter = labelFilter
188213
});
214+
189215
return this;
190216
}
191217

@@ -201,7 +227,14 @@ public AzureAppConfigurationOptions SelectSnapshot(string name)
201227
throw new ArgumentNullException(nameof(name));
202228
}
203229

204-
_kvSelectors.AppendUnique(new KeyValueSelector
230+
if (!_selectCalled)
231+
{
232+
_selectors.Clear();
233+
234+
_selectCalled = true;
235+
}
236+
237+
_selectors.AppendUnique(new KeyValueSelector
205238
{
206239
SnapshotName = name
207240
});
@@ -212,7 +245,7 @@ public AzureAppConfigurationOptions SelectSnapshot(string name)
212245
/// <summary>
213246
/// Configures options for Azure App Configuration feature flags that will be parsed and transformed into feature management configuration.
214247
/// If no filtering is specified via the <see cref="FeatureFlagOptions"/> then all feature flags with no label are loaded.
215-
/// All loaded feature flags will be automatically registered for refresh on an individual flag level.
248+
/// All loaded feature flags will be automatically registered for refresh as a collection.
216249
/// </summary>
217250
/// <param name="configure">A callback used to configure feature flag options.</param>
218251
public AzureAppConfigurationOptions UseFeatureFlags(Action<FeatureFlagOptions> configure = null)
@@ -237,25 +270,22 @@ public AzureAppConfigurationOptions UseFeatureFlags(Action<FeatureFlagOptions> c
237270
options.FeatureFlagSelectors.Add(new KeyValueSelector
238271
{
239272
KeyFilter = FeatureManagementConstants.FeatureFlagMarker + "*",
240-
LabelFilter = options.Label == null ? LabelFilter.Null : options.Label
273+
LabelFilter = string.IsNullOrWhiteSpace(options.Label) ? LabelFilter.Null : options.Label,
274+
IsFeatureFlagSelector = true
241275
});
242276
}
243277

244-
foreach (var featureFlagSelector in options.FeatureFlagSelectors)
278+
foreach (KeyValueSelector featureFlagSelector in options.FeatureFlagSelectors)
245279
{
246-
var featureFlagFilter = featureFlagSelector.KeyFilter;
247-
var labelFilter = featureFlagSelector.LabelFilter;
280+
_selectors.AppendUnique(featureFlagSelector);
248281

249-
Select(featureFlagFilter, labelFilter);
250-
251-
_multiKeyWatchers.AppendUnique(new KeyValueWatcher
282+
_ffWatchers.AppendUnique(new KeyValueWatcher
252283
{
253-
Key = featureFlagFilter,
254-
Label = labelFilter,
284+
Key = featureFlagSelector.KeyFilter,
285+
Label = featureFlagSelector.LabelFilter,
255286
// If UseFeatureFlags is called multiple times for the same key and label filters, last refresh interval wins
256287
RefreshInterval = options.RefreshInterval
257288
});
258-
259289
}
260290

261291
return this;
@@ -376,18 +406,41 @@ public AzureAppConfigurationOptions ConfigureClientOptions(Action<ConfigurationC
376406
/// <param name="configure">A callback used to configure Azure App Configuration refresh options.</param>
377407
public AzureAppConfigurationOptions ConfigureRefresh(Action<AzureAppConfigurationRefreshOptions> configure)
378408
{
409+
if (RegisterAllEnabled)
410+
{
411+
throw new InvalidOperationException($"{nameof(ConfigureRefresh)}() cannot be invoked multiple times when {nameof(AzureAppConfigurationRefreshOptions.RegisterAll)} has been invoked.");
412+
}
413+
379414
var refreshOptions = new AzureAppConfigurationRefreshOptions();
380415
configure?.Invoke(refreshOptions);
381416

382-
if (!refreshOptions.RefreshRegistrations.Any())
417+
bool isRegisterCalled = refreshOptions.RefreshRegistrations.Any();
418+
RegisterAllEnabled = refreshOptions.RegisterAllEnabled;
419+
420+
if (!isRegisterCalled && !RegisterAllEnabled)
421+
{
422+
throw new InvalidOperationException($"{nameof(ConfigureRefresh)}() must call either {nameof(AzureAppConfigurationRefreshOptions.Register)}()" +
423+
$" or {nameof(AzureAppConfigurationRefreshOptions.RegisterAll)}()");
424+
}
425+
426+
// Check if both register methods are called at any point
427+
if (RegisterAllEnabled && (_individualKvWatchers.Any() || isRegisterCalled))
383428
{
384-
throw new ArgumentException($"{nameof(ConfigureRefresh)}() must have at least one key-value registered for refresh.");
429+
throw new InvalidOperationException($"Cannot call both {nameof(AzureAppConfigurationRefreshOptions.RegisterAll)} and "
430+
+ $"{nameof(AzureAppConfigurationRefreshOptions.Register)}.");
385431
}
386432

387-
foreach (var item in refreshOptions.RefreshRegistrations)
433+
if (RegisterAllEnabled)
434+
{
435+
KvCollectionRefreshInterval = refreshOptions.RefreshInterval;
436+
}
437+
else
388438
{
389-
item.RefreshInterval = refreshOptions.RefreshInterval;
390-
_changeWatchers.Add(item);
439+
foreach (KeyValueWatcher item in refreshOptions.RefreshRegistrations)
440+
{
441+
item.RefreshInterval = refreshOptions.RefreshInterval;
442+
_individualKvWatchers.Add(item);
443+
}
391444
}
392445

393446
return this;

0 commit comments

Comments
 (0)