33//
44using Azure . Core ;
55using Azure . Data . AppConfiguration ;
6- using Microsoft . Extensions . Azure ;
76using Microsoft . Extensions . Configuration . AzureAppConfiguration . AzureKeyVault ;
87using Microsoft . Extensions . Configuration . AzureAppConfiguration . Extensions ;
98using Microsoft . Extensions . Configuration . AzureAppConfiguration . FeatureManagement ;
@@ -24,12 +23,13 @@ public class AzureAppConfigurationOptions
2423 private const int MaxRetries = 2 ;
2524 private static readonly TimeSpan MaxRetryDelay = TimeSpan . FromMinutes ( 1 ) ;
2625
27- private List < KeyValueWatcher > _changeWatchers = new List < KeyValueWatcher > ( ) ;
28- private List < KeyValueWatcher > _multiKeyWatchers = new List < KeyValueWatcher > ( ) ;
26+ private List < KeyValueWatcher > _individualKvWatchers = new List < KeyValueWatcher > ( ) ;
27+ private List < KeyValueWatcher > _ffWatchers = new List < KeyValueWatcher > ( ) ;
2928 private List < IKeyValueAdapter > _adapters ;
3029 private List < Func < ConfigurationSetting , ValueTask < ConfigurationSetting > > > _mappers = new List < Func < ConfigurationSetting , ValueTask < ConfigurationSetting > > > ( ) ;
31- private List < KeyValueSelector > _kvSelectors = new List < KeyValueSelector > ( ) ;
30+ private List < KeyValueSelector > _selectors ;
3231 private IConfigurationRefresher _refresher = new AzureAppConfigurationRefresher ( ) ;
32+ private bool _selectCalled = false ;
3333
3434 // The following set is sorted in descending order.
3535 // Since multiple prefixes could start with the same characters, we need to trim the longest prefix first.
@@ -63,19 +63,29 @@ public class AzureAppConfigurationOptions
6363 internal TokenCredential Credential { get ; private set ; }
6464
6565 /// <summary>
66- /// A collection of <see cref="KeyValueSelector"/>.
66+ /// A collection of <see cref="KeyValueSelector"/> specified by user .
6767 /// </summary>
68- 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 ; }
6979
7080 /// <summary>
7181 /// A collection of <see cref="KeyValueWatcher"/>.
7282 /// </summary>
73- internal IEnumerable < KeyValueWatcher > ChangeWatchers => _changeWatchers ;
83+ internal IEnumerable < KeyValueWatcher > IndividualKvWatchers => _individualKvWatchers ;
7484
7585 /// <summary>
7686 /// A collection of <see cref="KeyValueWatcher"/>.
7787 /// </summary>
78- internal IEnumerable < KeyValueWatcher > MultiKeyWatchers => _multiKeyWatchers ;
88+ internal IEnumerable < KeyValueWatcher > FeatureFlagWatchers => _ffWatchers ;
7989
8090 /// <summary>
8191 /// A collection of <see cref="IKeyValueAdapter"/>.
@@ -97,11 +107,15 @@ internal IEnumerable<IKeyValueAdapter> Adapters
97107 internal IEnumerable < string > KeyPrefixes => _keyPrefixes ;
98108
99109 /// <summary>
100- /// 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.
101111 /// </summary>
102- /// <remarks>This property is used only for unit testing.</remarks>
103112 internal IConfigurationClientManager ClientManager { get ; set ; }
104113
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+
105119 /// <summary>
106120 /// An optional timespan value to set the minimum backoff duration to a value other than the default.
107121 /// </summary>
@@ -132,11 +146,6 @@ internal IEnumerable<IKeyValueAdapter> Adapters
132146 /// </summary>
133147 internal StartupOptions Startup { get ; set ; } = new StartupOptions ( ) ;
134148
135- /// <summary>
136- /// Client factory that is responsible for creating instances of ConfigurationClient.
137- /// </summary>
138- internal IAzureClientFactory < ConfigurationClient > ClientFactory { get ; private set ; }
139-
140149 /// <summary>
141150 /// Initializes a new instance of the <see cref="AzureAppConfigurationOptions"/> class.
142151 /// </summary>
@@ -148,17 +157,9 @@ public AzureAppConfigurationOptions()
148157 new JsonKeyValueAdapter ( ) ,
149158 new FeatureManagementKeyValueAdapter ( FeatureFlagTracing )
150159 } ;
151- }
152160
153- /// <summary>
154- /// Sets the client factory used to create ConfigurationClient instances.
155- /// </summary>
156- /// <param name="factory">The client factory.</param>
157- /// <returns>The current <see cref="AzureAppConfigurationOptions"/> instance.</returns>
158- public AzureAppConfigurationOptions SetClientFactory ( IAzureClientFactory < ConfigurationClient > factory )
159- {
160- ClientFactory = factory ?? throw new ArgumentNullException ( nameof ( factory ) ) ;
161- return this ;
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 } } ;
162163 }
163164
164165 /// <summary>
@@ -187,22 +188,30 @@ public AzureAppConfigurationOptions Select(string keyFilter, string labelFilter
187188 throw new ArgumentNullException ( nameof ( keyFilter ) ) ;
188189 }
189190
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+
190197 if ( string . IsNullOrWhiteSpace ( labelFilter ) )
191198 {
192199 labelFilter = LabelFilter . Null ;
193200 }
194201
195- // Do not support * and , for label filter for now.
196- if ( labelFilter . Contains ( '*' ) || labelFilter . Contains ( ',' ) )
202+ if ( ! _selectCalled )
197203 {
198- throw new ArgumentException ( "The characters '*' and ',' are not supported in label filters." , nameof ( labelFilter ) ) ;
204+ _selectors . Clear ( ) ;
205+
206+ _selectCalled = true ;
199207 }
200208
201- _kvSelectors . AppendUnique ( new KeyValueSelector
209+ _selectors . AppendUnique ( new KeyValueSelector
202210 {
203211 KeyFilter = keyFilter ,
204212 LabelFilter = labelFilter
205213 } ) ;
214+
206215 return this ;
207216 }
208217
@@ -218,7 +227,14 @@ public AzureAppConfigurationOptions SelectSnapshot(string name)
218227 throw new ArgumentNullException ( nameof ( name ) ) ;
219228 }
220229
221- _kvSelectors . AppendUnique ( new KeyValueSelector
230+ if ( ! _selectCalled )
231+ {
232+ _selectors . Clear ( ) ;
233+
234+ _selectCalled = true ;
235+ }
236+
237+ _selectors . AppendUnique ( new KeyValueSelector
222238 {
223239 SnapshotName = name
224240 } ) ;
@@ -229,7 +245,7 @@ public AzureAppConfigurationOptions SelectSnapshot(string name)
229245 /// <summary>
230246 /// Configures options for Azure App Configuration feature flags that will be parsed and transformed into feature management configuration.
231247 /// If no filtering is specified via the <see cref="FeatureFlagOptions"/> then all feature flags with no label are loaded.
232- /// 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 .
233249 /// </summary>
234250 /// <param name="configure">A callback used to configure feature flag options.</param>
235251 public AzureAppConfigurationOptions UseFeatureFlags ( Action < FeatureFlagOptions > configure = null )
@@ -254,25 +270,22 @@ public AzureAppConfigurationOptions UseFeatureFlags(Action<FeatureFlagOptions> c
254270 options . FeatureFlagSelectors . Add ( new KeyValueSelector
255271 {
256272 KeyFilter = FeatureManagementConstants . FeatureFlagMarker + "*" ,
257- LabelFilter = options . Label == null ? LabelFilter . Null : options . Label
273+ LabelFilter = string . IsNullOrWhiteSpace ( options . Label ) ? LabelFilter . Null : options . Label ,
274+ IsFeatureFlagSelector = true
258275 } ) ;
259276 }
260277
261- foreach ( var featureFlagSelector in options . FeatureFlagSelectors )
278+ foreach ( KeyValueSelector featureFlagSelector in options . FeatureFlagSelectors )
262279 {
263- var featureFlagFilter = featureFlagSelector . KeyFilter ;
264- var labelFilter = featureFlagSelector . LabelFilter ;
280+ _selectors . AppendUnique ( featureFlagSelector ) ;
265281
266- Select ( featureFlagFilter , labelFilter ) ;
267-
268- _multiKeyWatchers . AppendUnique ( new KeyValueWatcher
282+ _ffWatchers . AppendUnique ( new KeyValueWatcher
269283 {
270- Key = featureFlagFilter ,
271- Label = labelFilter ,
284+ Key = featureFlagSelector . KeyFilter ,
285+ Label = featureFlagSelector . LabelFilter ,
272286 // If UseFeatureFlags is called multiple times for the same key and label filters, last refresh interval wins
273287 RefreshInterval = options . RefreshInterval
274288 } ) ;
275-
276289 }
277290
278291 return this ;
@@ -393,18 +406,41 @@ public AzureAppConfigurationOptions ConfigureClientOptions(Action<ConfigurationC
393406 /// <param name="configure">A callback used to configure Azure App Configuration refresh options.</param>
394407 public AzureAppConfigurationOptions ConfigureRefresh ( Action < AzureAppConfigurationRefreshOptions > configure )
395408 {
409+ if ( RegisterAllEnabled )
410+ {
411+ throw new InvalidOperationException ( $ "{ nameof ( ConfigureRefresh ) } () cannot be invoked multiple times when { nameof ( AzureAppConfigurationRefreshOptions . RegisterAll ) } has been invoked.") ;
412+ }
413+
396414 var refreshOptions = new AzureAppConfigurationRefreshOptions ( ) ;
397415 configure ? . Invoke ( refreshOptions ) ;
398416
399- 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 ) )
400428 {
401- 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 ) } .") ;
402431 }
403432
404- foreach ( var item in refreshOptions . RefreshRegistrations )
433+ if ( RegisterAllEnabled )
405434 {
406- item . RefreshInterval = refreshOptions . RefreshInterval ;
407- _changeWatchers . Add ( item ) ;
435+ KvCollectionRefreshInterval = refreshOptions . RefreshInterval ;
436+ }
437+ else
438+ {
439+ foreach ( KeyValueWatcher item in refreshOptions . RefreshRegistrations )
440+ {
441+ item . RefreshInterval = refreshOptions . RefreshInterval ;
442+ _individualKvWatchers . Add ( item ) ;
443+ }
408444 }
409445
410446 return this ;
0 commit comments