@@ -24,12 +24,13 @@ public class AzureAppConfigurationOptions
2424 private const int MaxRetries = 2 ;
2525 private static readonly TimeSpan MaxRetryDelay = TimeSpan . FromMinutes ( 1 ) ;
2626
27- private List < KeyValueWatcher > _changeWatchers = new List < KeyValueWatcher > ( ) ;
28- private List < KeyValueWatcher > _multiKeyWatchers = new List < KeyValueWatcher > ( ) ;
27+ private List < KeyValueWatcher > _individualKvWatchers = new List < KeyValueWatcher > ( ) ;
28+ private List < KeyValueWatcher > _ffWatchers = new List < KeyValueWatcher > ( ) ;
2929 private List < IKeyValueAdapter > _adapters ;
3030 private List < Func < ConfigurationSetting , ValueTask < ConfigurationSetting > > > _mappers = new List < Func < ConfigurationSetting , ValueTask < ConfigurationSetting > > > ( ) ;
31- private List < KeyValueSelector > _kvSelectors = new List < KeyValueSelector > ( ) ;
31+ private List < KeyValueSelector > _selectors ;
3232 private IConfigurationRefresher _refresher = new AzureAppConfigurationRefresher ( ) ;
33+ private bool _selectCalled = false ;
3334
3435 // The following set is sorted in descending order.
3536 // Since multiple prefixes could start with the same characters, we need to trim the longest prefix first.
@@ -63,19 +64,29 @@ public class AzureAppConfigurationOptions
6364 internal TokenCredential Credential { get ; private set ; }
6465
6566 /// <summary>
66- /// A collection of <see cref="KeyValueSelector"/>.
67+ /// A collection of <see cref="KeyValueSelector"/> specified by user .
6768 /// </summary>
68- internal IEnumerable < KeyValueSelector > KeyValueSelectors => _kvSelectors ;
69+ internal IEnumerable < KeyValueSelector > Selectors => _selectors ;
70+
71+ /// <summary>
72+ /// Indicates if <see cref="AzureAppConfigurationRefreshOptions.RegisterAll"/> was called.
73+ /// </summary>
74+ internal bool RegisterAllEnabled { get ; private set ; }
75+
76+ /// <summary>
77+ /// Refresh interval for selected key-value collections when <see cref="AzureAppConfigurationRefreshOptions.RegisterAll"/> is called.
78+ /// </summary>
79+ internal TimeSpan KvCollectionRefreshInterval { get ; private set ; }
6980
7081 /// <summary>
7182 /// A collection of <see cref="KeyValueWatcher"/>.
7283 /// </summary>
73- internal IEnumerable < KeyValueWatcher > ChangeWatchers => _changeWatchers ;
84+ internal IEnumerable < KeyValueWatcher > IndividualKvWatchers => _individualKvWatchers ;
7485
7586 /// <summary>
7687 /// A collection of <see cref="KeyValueWatcher"/>.
7788 /// </summary>
78- internal IEnumerable < KeyValueWatcher > MultiKeyWatchers => _multiKeyWatchers ;
89+ internal IEnumerable < KeyValueWatcher > FeatureFlagWatchers => _ffWatchers ;
7990
8091 /// <summary>
8192 /// A collection of <see cref="IKeyValueAdapter"/>.
@@ -97,11 +108,15 @@ internal IEnumerable<IKeyValueAdapter> Adapters
97108 internal IEnumerable < string > KeyPrefixes => _keyPrefixes ;
98109
99110 /// <summary>
100- /// An optional configuration client manager that can be used to provide clients to communicate with Azure App Configuration.
111+ /// For use in tests only. An optional configuration client manager that can be used to provide clients to communicate with Azure App Configuration.
101112 /// </summary>
102- /// <remarks>This property is used only for unit testing.</remarks>
103113 internal IConfigurationClientManager ClientManager { get ; set ; }
104114
115+ /// <summary>
116+ /// For use in tests only. An optional class used to process pageable results from Azure App Configuration.
117+ /// </summary>
118+ internal IConfigurationSettingPageIterator ConfigurationSettingPageIterator { get ; set ; }
119+
105120 /// <summary>
106121 /// An optional timespan value to set the minimum backoff duration to a value other than the default.
107122 /// </summary>
@@ -148,6 +163,9 @@ public AzureAppConfigurationOptions()
148163 new JsonKeyValueAdapter ( ) ,
149164 new FeatureManagementKeyValueAdapter ( FeatureFlagTracing )
150165 } ;
166+
167+ // Adds the default query to App Configuration if <see cref="Select"/> and <see cref="SelectSnapshot"/> are never called.
168+ _selectors = new List < KeyValueSelector > { new KeyValueSelector { KeyFilter = KeyFilter . Any , LabelFilter = LabelFilter . Null } } ;
151169 }
152170
153171 /// <summary>
@@ -187,22 +205,30 @@ public AzureAppConfigurationOptions Select(string keyFilter, string labelFilter
187205 throw new ArgumentNullException ( nameof ( keyFilter ) ) ;
188206 }
189207
208+ // Do not support * and , for label filter for now.
209+ if ( labelFilter != null && ( labelFilter . Contains ( '*' ) || labelFilter . Contains ( ',' ) ) )
210+ {
211+ throw new ArgumentException ( "The characters '*' and ',' are not supported in label filters." , nameof ( labelFilter ) ) ;
212+ }
213+
190214 if ( string . IsNullOrWhiteSpace ( labelFilter ) )
191215 {
192216 labelFilter = LabelFilter . Null ;
193217 }
194218
195- // Do not support * and , for label filter for now.
196- if ( labelFilter . Contains ( '*' ) || labelFilter . Contains ( ',' ) )
219+ if ( ! _selectCalled )
197220 {
198- throw new ArgumentException ( "The characters '*' and ',' are not supported in label filters." , nameof ( labelFilter ) ) ;
221+ _selectors . Clear ( ) ;
222+
223+ _selectCalled = true ;
199224 }
200225
201- _kvSelectors . AppendUnique ( new KeyValueSelector
226+ _selectors . AppendUnique ( new KeyValueSelector
202227 {
203228 KeyFilter = keyFilter ,
204229 LabelFilter = labelFilter
205230 } ) ;
231+
206232 return this ;
207233 }
208234
@@ -218,7 +244,14 @@ public AzureAppConfigurationOptions SelectSnapshot(string name)
218244 throw new ArgumentNullException ( nameof ( name ) ) ;
219245 }
220246
221- _kvSelectors . AppendUnique ( new KeyValueSelector
247+ if ( ! _selectCalled )
248+ {
249+ _selectors . Clear ( ) ;
250+
251+ _selectCalled = true ;
252+ }
253+
254+ _selectors . AppendUnique ( new KeyValueSelector
222255 {
223256 SnapshotName = name
224257 } ) ;
@@ -229,7 +262,7 @@ public AzureAppConfigurationOptions SelectSnapshot(string name)
229262 /// <summary>
230263 /// Configures options for Azure App Configuration feature flags that will be parsed and transformed into feature management configuration.
231264 /// 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 .
265+ /// All loaded feature flags will be automatically registered for refresh as a collection .
233266 /// </summary>
234267 /// <param name="configure">A callback used to configure feature flag options.</param>
235268 public AzureAppConfigurationOptions UseFeatureFlags ( Action < FeatureFlagOptions > configure = null )
@@ -254,25 +287,22 @@ public AzureAppConfigurationOptions UseFeatureFlags(Action<FeatureFlagOptions> c
254287 options . FeatureFlagSelectors . Add ( new KeyValueSelector
255288 {
256289 KeyFilter = FeatureManagementConstants . FeatureFlagMarker + "*" ,
257- LabelFilter = options . Label == null ? LabelFilter . Null : options . Label
290+ LabelFilter = string . IsNullOrWhiteSpace ( options . Label ) ? LabelFilter . Null : options . Label ,
291+ IsFeatureFlagSelector = true
258292 } ) ;
259293 }
260294
261- foreach ( var featureFlagSelector in options . FeatureFlagSelectors )
295+ foreach ( KeyValueSelector featureFlagSelector in options . FeatureFlagSelectors )
262296 {
263- var featureFlagFilter = featureFlagSelector . KeyFilter ;
264- var labelFilter = featureFlagSelector . LabelFilter ;
297+ _selectors . AppendUnique ( featureFlagSelector ) ;
265298
266- Select ( featureFlagFilter , labelFilter ) ;
267-
268- _multiKeyWatchers . AppendUnique ( new KeyValueWatcher
299+ _ffWatchers . AppendUnique ( new KeyValueWatcher
269300 {
270- Key = featureFlagFilter ,
271- Label = labelFilter ,
301+ Key = featureFlagSelector . KeyFilter ,
302+ Label = featureFlagSelector . LabelFilter ,
272303 // If UseFeatureFlags is called multiple times for the same key and label filters, last refresh interval wins
273304 RefreshInterval = options . RefreshInterval
274305 } ) ;
275-
276306 }
277307
278308 return this ;
@@ -424,18 +454,41 @@ public AzureAppConfigurationOptions ConfigureClientOptions(Action<ConfigurationC
424454 /// <param name="configure">A callback used to configure Azure App Configuration refresh options.</param>
425455 public AzureAppConfigurationOptions ConfigureRefresh ( Action < AzureAppConfigurationRefreshOptions > configure )
426456 {
457+ if ( RegisterAllEnabled )
458+ {
459+ throw new InvalidOperationException ( $ "{ nameof ( ConfigureRefresh ) } () cannot be invoked multiple times when { nameof ( AzureAppConfigurationRefreshOptions . RegisterAll ) } has been invoked.") ;
460+ }
461+
427462 var refreshOptions = new AzureAppConfigurationRefreshOptions ( ) ;
428463 configure ? . Invoke ( refreshOptions ) ;
429464
430- if ( ! refreshOptions . RefreshRegistrations . Any ( ) )
465+ bool isRegisterCalled = refreshOptions . RefreshRegistrations . Any ( ) ;
466+ RegisterAllEnabled = refreshOptions . RegisterAllEnabled ;
467+
468+ if ( ! isRegisterCalled && ! RegisterAllEnabled )
469+ {
470+ throw new InvalidOperationException ( $ "{ nameof ( ConfigureRefresh ) } () must call either { nameof ( AzureAppConfigurationRefreshOptions . Register ) } ()" +
471+ $ " or { nameof ( AzureAppConfigurationRefreshOptions . RegisterAll ) } ()") ;
472+ }
473+
474+ // Check if both register methods are called at any point
475+ if ( RegisterAllEnabled && ( _individualKvWatchers . Any ( ) || isRegisterCalled ) )
431476 {
432- throw new ArgumentException ( $ "{ nameof ( ConfigureRefresh ) } () must have at least one key-value registered for refresh.") ;
477+ throw new InvalidOperationException ( $ "Cannot call both { nameof ( AzureAppConfigurationRefreshOptions . RegisterAll ) } and "
478+ + $ "{ nameof ( AzureAppConfigurationRefreshOptions . Register ) } .") ;
433479 }
434480
435- foreach ( var item in refreshOptions . RefreshRegistrations )
481+ if ( RegisterAllEnabled )
482+ {
483+ KvCollectionRefreshInterval = refreshOptions . RefreshInterval ;
484+ }
485+ else
436486 {
437- item . RefreshInterval = refreshOptions . RefreshInterval ;
438- _changeWatchers . Add ( item ) ;
487+ foreach ( KeyValueWatcher item in refreshOptions . RefreshRegistrations )
488+ {
489+ item . RefreshInterval = refreshOptions . RefreshInterval ;
490+ _individualKvWatchers . Add ( item ) ;
491+ }
439492 }
440493
441494 return this ;
0 commit comments