@@ -57,12 +57,13 @@ This roadmap is based on comprehensive analysis of:
5757- ** Custom validation** - ` IValidateOptions<T> ` for complex business rules beyond DataAnnotations
5858- ** Named options** - Multiple configurations of the same options type with different names
5959- ** Error on missing keys** - ` ErrorOnMissingKeys ` fail-fast validation when configuration sections are missing
60+ - ** Configuration change callbacks** - ` OnChange ` callbacks for Monitor lifetime (auto-generates IHostedService)
6061- ** Lifetime selection** - Singleton (` IOptions ` ), Scoped (` IOptionsSnapshot ` ), Monitor (` IOptionsMonitor ` )
6162- ** Multi-project support** - Assembly-specific extension methods with smart naming
6263- ** Transitive registration** - 4 overloads for automatic/selective assembly registration
6364- ** Partial class requirement** - Enforced at compile time
6465- ** Native AOT compatible** - Zero reflection, compile-time generation
65- - ** Compile-time diagnostics** - Validate partial class, section names
66+ - ** Compile-time diagnostics** - Validate partial class, section names, OnChange callbacks (ATCOPT001-007)
6667
6768---
6869
@@ -74,7 +75,7 @@ This roadmap is based on comprehensive analysis of:
7475| ✅ | [ Named Options Support] ( #2-named-options-support ) | 🔴 High |
7576| ❌ | [ Post-Configuration Support] ( #3-post-configuration-support ) | 🟡 Medium-High |
7677| ✅ | [ Error on Missing Configuration Keys] ( #4-error-on-missing-configuration-keys ) | 🔴 High |
77- | ❌ | [ Configuration Change Callbacks] ( #5-configuration-change-callbacks ) | 🟡 Medium |
78+ | ✅ | [ Configuration Change Callbacks] ( #5-configuration-change-callbacks ) | 🟡 Medium |
7879| ❌ | [ Bind Configuration Subsections to Properties] ( #6-bind-configuration-subsections-to-properties ) | 🟡 Medium |
7980| ❌ | [ ConfigureAll Support] ( #7-configureall-support ) | 🟢 Low-Medium |
8081| ❌ | [ Options Snapshots for Specific Sections] ( #8-options-snapshots-for-specific-sections ) | 🟢 Low-Medium |
@@ -237,6 +238,7 @@ public class DataService
237238- ⚠️ Named options do NOT support validation chain (ValidateDataAnnotations, ValidateOnStart, Validator)
238239
239240** Testing** :
241+
240242- ✅ 8 comprehensive unit tests covering all scenarios
241243- ✅ Sample project with EmailOptions demonstrating Primary/Secondary/Fallback servers
242244- ✅ PetStore.Api sample with NotificationOptions (Email/SMS/Push channels)
@@ -350,11 +352,13 @@ services.AddOptions<DatabaseOptions>()
350352- ⚠️ Named options do NOT support ErrorOnMissingKeys (named options use simpler Configure pattern)
351353
352354** Testing** :
355+
353356- ✅ 11 comprehensive unit tests covering all scenarios
354357- ✅ Sample project updated: DatabaseOptions demonstrates ErrorOnMissingKeys
355358- ✅ PetStore.Api sample: PetStoreOptions uses ErrorOnMissingKeys for critical configuration
356359
357360** Best Practices** :
361+
358362- Always combine with ` ValidateOnStart = true ` to catch missing configuration at startup
359363- Use for production-critical configuration (databases, external services, API keys)
360364- Avoid for optional configuration with reasonable defaults
@@ -364,7 +368,7 @@ services.AddOptions<DatabaseOptions>()
364368### 5. Configuration Change Callbacks
365369
366370** Priority** : 🟡 ** Medium**
367- ** Status** : ❌ Not Implemented
371+ ** Status** : ✅ ** Implemented**
368372** Inspiration** : ` IOptionsMonitor<T>.OnChange() ` pattern
369373
370374** Description** : Support registering callbacks that execute when configuration changes are detected.
@@ -383,24 +387,70 @@ public partial class FeaturesOptions
383387 public int MaxUploadSizeMB { get ; set ; } = 10 ;
384388
385389 // Change callback - signature: static void OnChange(TOptions options, string? name)
386- private static void OnFeaturesChanged (FeaturesOptions options , string ? name )
390+ internal static void OnFeaturesChanged (FeaturesOptions options , string ? name )
387391 {
388392 Console .WriteLine ($" Features configuration changed: EnableNewUI={options .EnableNewUI }" );
389393 // Clear caches, notify components, etc.
390394 }
391395}
392396
393397// Generated code:
394- var monitor = services .BuildServiceProvider ().GetRequiredService <IOptionsMonitor <FeaturesOptions >>();
395- monitor .OnChange ((options , name ) => FeaturesOptions .OnFeaturesChanged (options , name ));
398+ services .AddOptions <FeaturesOptions >()
399+ .Bind (configuration .GetSection (" Features" ));
400+
401+ services .AddHostedService <FeaturesOptionsChangeListener >();
402+
403+ // Generated hosted service
404+ internal sealed class FeaturesOptionsChangeListener : IHostedService
405+ {
406+ private readonly IOptionsMonitor <FeaturesOptions > _monitor ;
407+ private IDisposable ? _changeToken ;
408+
409+ public FeaturesOptionsChangeListener (IOptionsMonitor <FeaturesOptions > monitor )
410+ {
411+ _monitor = monitor ?? throw new ArgumentNullException (nameof (monitor ));
412+ }
413+
414+ public Task StartAsync (CancellationToken cancellationToken )
415+ {
416+ _changeToken = _monitor .OnChange ((options , name ) =>
417+ FeaturesOptions .OnFeaturesChanged (options , name ));
418+ return Task .CompletedTask ;
419+ }
420+
421+ public Task StopAsync (CancellationToken cancellationToken )
422+ {
423+ _changeToken ? .Dispose ();
424+ return Task .CompletedTask ;
425+ }
426+ }
396427```
397428
398- ** Implementation Notes** :
429+ ** Implementation Details** :
430+
431+ - ✅ Added ` OnChange ` property to ` [OptionsBinding] ` attribute
432+ - ✅ Generator creates ` IHostedService ` that registers the callback via ` IOptionsMonitor<T>.OnChange() `
433+ - ✅ Hosted service is automatically registered when application starts
434+ - ✅ Callback signature: ` static void MethodName(TOptions options, string? name) `
435+ - ✅ Callback method can be ` internal ` or ` public ` (not ` private ` )
436+ - ✅ Properly disposes change token in ` StopAsync ` to prevent memory leaks
437+ - ✅ Only applicable when ` Lifetime = OptionsLifetime.Monitor `
438+ - ⚠️ Cannot be used with named options
439+ - ✅ Comprehensive compile-time validation with 4 diagnostic codes (ATCOPT004-007)
440+ - ** Limitation** : Only works with file-based configuration providers (appsettings.json with reloadOnChange: true)
441+
442+ ** Diagnostics** :
443+
444+ - ** ATCOPT004** : OnChange callback requires Monitor lifetime
445+ - ** ATCOPT005** : OnChange callback not supported with named options
446+ - ** ATCOPT006** : OnChange callback method not found
447+ - ** ATCOPT007** : OnChange callback method has invalid signature
448+
449+ ** Testing** :
399450
400- - Only applicable when ` Lifetime = OptionsLifetime.Monitor `
401- - Callback signature: ` static void OnChange(TOptions options, string? name) `
402- - Useful for feature flags, dynamic configuration
403- - ** Limitation** : Only works with file-based configuration providers (appsettings.json)
451+ - ✅ 20 comprehensive unit tests covering all scenarios and error cases
452+ - ✅ Sample project updated: LoggingOptions demonstrates OnChange callbacks
453+ - ✅ PetStore.Api sample: FeaturesOptions uses OnChange for feature flag changes
404454
405455---
406456
0 commit comments