Loading... 最近在使用`IOptionsMonitor`时,发现`OnChange`方法无法回调,代码如下: #### `Startup.cs` ```csharp public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.Configure<MyConfig>(config => { config.Value = Configuration.GetValue<string>("MyConfig:Value"); }); services.AddSingleton<SingletonService>(); } ``` #### `SingletonService.cs` ```csharp private MyConfig myConfig; public SingletonService(IOptionsMonitor<MyConfig> optionsMonitor) { myConfig = optionsMonitor.CurrentValue; optionsMonitor.OnChange(myconfig => { System.Console.WriteLine("=================Changed============"); System.Console.WriteLine($"Before: {myConfig.Value}\nAfter: {myconfig.Value}"); this.myConfig = myconfig; }); } ``` #### `appsettings.json` ```json { "MyConfig": { "Value": "MyValue" } } ``` 在程序运行时,修改`appsettings.json`后,`optionsMonitor.OnChange`的回调方法没有被触发。 解决方案有两种: #### `Startup.cs` ```csharp public void ConfigureServices(IServiceCollection services) { services.AddControllers(); //services.Configure<MyConfig>(config => //{ // config.Value = Configuration.GetValue<string>("MyConfig:Value"); //}); //使用Configuration.GetSection()方法 services.Configure<MyConfig>(Configuration.GetSection("MyConfig")); services.AddSingleton<SingletonService>(); } ``` #### 或者 #### `Startup.cs` ```csharp public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.Configure<MyConfig>(config => { config.Value = Configuration.GetValue<string>("MyConfig:Value"); }); //注册ConfigurationChangeTokenSource services.AddSingleton<IOptionsChangeTokenSource<MyConfig>>(new ConfigurationChangeTokenSource<MyConfig>(Configuration)); services.AddSingleton<SingletonService>(); } ``` 为何使用`services.Configure<MyConfig>(Configuration.GetSection("MyConfig"))`即可正确触发`optionsMonitor.OnChange`的回调方法呢?让我们跟随[ASP.NET Extensions](https://github.com/aspnet/Extensions)源码来一探究竟。 #### [`OptionsConfigurationServiceCollectionExtensions.cs`](https://github.com/aspnet/Extensions/tree/master/src/Options/ConfigurationExtensions/src/OptionsConfigurationServiceCollectionExtensions.cs) 我们先来看看`services.Configure<TOptions>(Configuration.GetSection())`方法究竟干了什么: ```csharp public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder) where TOptions : class { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (config == null) { throw new ArgumentNullException(nameof(config)); } services.AddOptions(); services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config)); return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder)); } ``` 这里关键的一句即是`services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config))`,这句话有什么用呢,它向容器注册了一个`ConfigurationChangeTokenSource<TOptions>`实例,这个类实现了`IOptionsChangeTokenSource<TOptions>`,OK,我们不用管`IOptionsChangeTokenSource<TOptions>`是啥玩意,我们只需要看`ConfigurationChangeTokenSource<TOptions>`这个类干了什么: #### [`ConfigurationChangeTokenSource.cs`](https://github.com/aspnet/Extensions/tree/master/src/Options/ConfigurationExtensions/src/ConfigurationChangeTokenSource.cs) ```csharp public class ConfigurationChangeTokenSource<TOptions> : IOptionsChangeTokenSource<TOptions> { private IConfiguration _config; /// <summary> /// Constructor taking the <see cref="IConfiguration"/> instance to watch. /// </summary> /// <param name="config">The configuration instance.</param> public ConfigurationChangeTokenSource(IConfiguration config) : this(Options.DefaultName, config) { } /// <summary> /// Constructor taking the <see cref="IConfiguration"/> instance to watch. /// </summary> /// <param name="name">The name of the options instance being watched.</param> /// <param name="config">The configuration instance.</param> public ConfigurationChangeTokenSource(string name, IConfiguration config) { if (config == null) { throw new ArgumentNullException(nameof(config)); } _config = config; Name = name ?? Options.DefaultName; } /// <summary> /// The name of the option instance being changed. /// </summary> public string Name { get; } /// <summary> /// Returns the reloadToken from the <see cref="IConfiguration"/>. /// </summary> /// <returns></returns> public IChangeToken GetChangeToken() { return _config.GetReloadToken(); } } ``` 这个类从`IConfiguration`拿到了一个`IChangeToken`,它是配置文件发生变更的回调令牌。将`ConfigurationChangeTokenSource`注入容器后,`OptionsMonitor`的构造函数即可使用它的`IChangeToken`令牌来回调`OptionsMonitor.OnChange`方法: #### [`OptionsMonitor.cs`](https://github.com/aspnet/Extensions/tree/master/src/Options/Options/src/OptionsMonitor.cs) ```csharp /// <summary> /// Implementation of <see cref="IOptionsMonitor{TOptions}"/>. /// </summary> /// <typeparam name="TOptions"></typeparam> public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IDisposable where TOptions : class, new() { private readonly IOptionsMonitorCache<TOptions> _cache; private readonly IOptionsFactory<TOptions> _factory; private readonly IEnumerable<IOptionsChangeTokenSource<TOptions>> _sources; private readonly List<IDisposable> _registrations = new List<IDisposable>(); internal event Action<TOptions, string> _onChange; /// <summary> /// Constructor. /// </summary> /// <param name="factory">The factory to use to create options.</param> /// <param name="sources">The sources used to listen for changes to the options instance.</param> /// <param name="cache">The cache used to store options.</param> public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache) { _factory = factory; _sources = sources; _cache = cache; foreach (var source in _sources) { var registration = ChangeToken.OnChange( () => source.GetChangeToken(), (name) => InvokeChanged(name), source.Name); _registrations.Add(registration); } } private void InvokeChanged(string name) { name = name ?? Options.DefaultName; _cache.TryRemove(name); var options = Get(name); if (_onChange != null) { _onChange.Invoke(options, name); } } /// <summary> /// Registers a listener to be called whenever <typeparamref name="TOptions"/> changes. /// </summary> /// <param name="listener">The action to be invoked when <typeparamref name="TOptions"/> has changed.</param> /// <returns>An <see cref="IDisposable"/> which should be disposed to stop listening for changes.</returns> public IDisposable OnChange(Action<TOptions, string> listener) { var disposable = new ChangeTrackerDisposable(this, listener); _onChange += disposable.OnChange; return disposable; } internal class ChangeTrackerDisposable : IDisposable { private readonly Action<TOptions, string> _listener; private readonly OptionsMonitor<TOptions> _monitor; public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions, string> listener) { _listener = listener; _monitor = monitor; } public void OnChange(TOptions options, string name) => _listener.Invoke(options, name); public void Dispose() => _monitor._onChange -= OnChange; } } ``` 在`OptionsMonitor`的构造函数中,使用`ChangeToken.OnChange`来注册自己的`InvokeChanged`方法,当令牌发生回调时,`InvokeChanged`便会执行,`OnChange`方法也在`InvokeChanged`发生回调。 那么到目前为止,令牌来自于`IConfiguration`接口,它是如何知道配置文件发生变更呢? 下次再更。。。 最后修改:2020 年 04 月 14 日 © 允许规范转载 赞 0 如果觉得我的文章对你有用,请随意赞赏