NET Core IOptionsMonitor的使用
最近在使用IOptionsMonitor时,发现OnChange方法无法回调,代码如下:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.Configure<MyConfig>(config =>
{
config.Value = Configuration.GetValue<string>("MyConfig:Value");
});
services.AddSingleton<SingletonService>();
}
SingletonService.cs
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
{
"MyConfig": {
"Value": "MyValue"
}
}
在程序运行时,修改appsettings.json后,optionsMonitor.OnChange的回调方法没有被触发。
解决方案有两种:
Startup.cs
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
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源码来一探究竟。
OptionsConfigurationServiceCollectionExtensions.cs
我们先来看看services.Configure<TOptions>(Configuration.GetSection())方法究竟干了什么:
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
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
/// <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接口,它是如何知道配置文件发生变更呢?
下次再更。。。