前面我們講到 IOptions 和 IOptionsSnapshot,他們兩個最大的區別便是前者注冊的是單例模式,后者注冊的是 Scope 模式。而 IOptionsMonitor 則要求配置源必須是可監聽的,用來實現 Options 實例的自動更新,並對外提供了 OnChage 事件,給我們更多的控制權。
目錄
- IOptionsMonitor
- OptionsMonitor源碼探索
- ConfigurationChangeTokenSource
- 示例
IOptionsMonitor
對於 IOptionsMonitor 我們接觸的較少,它的定義如下:
public interface IOptionsMonitor<out TOptions>
{
TOptions CurrentValue { get; }
TOptions Get(string name);
IDisposable OnChange(Action<TOptions, string> listener);
}
在AddOptions
擴展方法中,可以看到它的默認實現為 OptionsMonitor:
public static IServiceCollection AddOptions(this IServiceCollection services)
{
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
}
OptionsMonitor源碼探索
OptionsMonitor通過IOptionsChangeTokenSource
來實現事件的監聽,具體如下:
public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions> where TOptions : class, new()
{
private readonly IOptionsMonitorCache<TOptions> _cache;
private readonly IOptionsFactory<TOptions> _factory;
private readonly IEnumerable<IOptionsChangeTokenSource<TOptions>> _sources;
internal event Action<TOptions, string> _onChange;
public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
{
_factory = factory;
_sources = sources;
_cache = cache;
foreach (var source in _sources)
{
ChangeToken.OnChange<string>(() => source.GetChangeToken(), (name) => InvokeChanged(name), source.Name);
}
}
public TOptions CurrentValue => Get(Options.DefaultName);
public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
return _cache.GetOrAdd(name, () => _factory.Create(name));
}
public IDisposable OnChange(Action<TOptions, string> listener)
{
...
}
private void InvokeChanged(string name)
{
...
}
}
首先看構造函數中的三個參數,其中 IOptionsFactory<>
在上一章已講過,而IOptionsChangeTokenSource
則在 第一章 中介紹過,通過IConfiguration
進行配置的 Options,會注冊該類型的實現,用來實現對配置源的監聽:
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config) where TOptions : class
{
...
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
...
}
IOptionsChangeTokenSource 的定義如下:
public interface IOptionsChangeTokenSource<out TOptions>
{
IChangeToken GetChangeToken();
string Name { get; }
}
在OptionsMonitor
的構造函數中,通過調用其GetChangeToken
方法,獲取到 ChangeToken ,在 InvokeChanged
完成 _onChange 事件的調用:
private void InvokeChanged(string name)
{
name = name ?? Options.DefaultName;
_cache.TryRemove(name);
var options = Get(name);
if (_onChange != null)
{
_onChange.Invoke(options, name);
}
}
而對外暴露的OnChange
方法,方便我們注冊自己的邏輯:
public IDisposable OnChange(Action<TOptions> listener)
{
var disposable = new ChangeTrackerDisposable(this, listener);
_onChange += disposable.OnChange;
return disposable;
}
這里又使用了一個 ChangeTrackerDisposable 的包裝類,用來實現事件的注銷:
internal class ChangeTrackerDisposable : IDisposable
{
private readonly Action<TOptions> _listener;
private readonly OptionsMonitor<TOptions> _monitor;
public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions> listener)
{
_listener = listener;
_monitor = monitor;
}
public void OnChange(TOptions options) => _listener.Invoke(options);
public void Dispose() => _monitor._onChange -= OnChange;
}
構造函數的最后一個參數IOptionsMonitorCache
的默認實現便是 上一章 中介紹的 OptionsCache
。
ConfigurationChangeTokenSource
IConfigurationChangeTokenSource 的默認實現類是 ConfigurationChangeTokenSource
:
public class ConfigurationChangeTokenSource<TOptions> : IOptionsChangeTokenSource<TOptions>
{
private IConfiguration _config;
public ConfigurationChangeTokenSource(IConfiguration config) : this(Options.DefaultName, config) { }
public ConfigurationChangeTokenSource(string name, IConfiguration config)
{
if (config == null)
{
throw new ArgumentNullException(nameof(config));
}
_config = config;
}
public string Name { get; }
public IChangeToken GetChangeToken()
{
return _config.GetReloadToken();
}
}
上面用到的 ChangeToken 便是通過構造函數接受的IConfiguration
類型的參數來獲取的:
public interface IConfiguration
{
...
IChangeToken GetReloadToken();
...
}
因此要想使用IOptionsMonitor
,通常要使用IConfiguration
來注冊才可以,當然,你也可以實現自己的 ConfigurationChangeTokenSource
。
示例
下面簡單演示一下IOptionsMonitor
的使用:
首先創建一個控制台程序,添加appsettings.json
:
{
"Name": "bob"
}
然后修改Program.cs
如下:
public class MyOptions
{
public string Name { get; set; }
}
class Program
{
private IOptionsMonitor<MyOptions> _options;
public Program()
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.Configure<MyOptions>(configuration);
var serviceProvider = serviceCollection.BuildServiceProvider();
_options = serviceProvider.GetRequiredService<IOptionsMonitor<MyOptions>>();
}
public static void Main(string[] args)
{
new Program().Execute(args);
}
public void Execute(string[] args)
{
Console.WriteLine(_options.CurrentValue.Name);
_options.OnChange(_ => Console.WriteLine(_.Name));
Console.ReadKey();
}
}
我們手動修改配置文件,便會觸發OnChange事件。
附示例代碼地址:https://github.com/RainingNight/AspNetCoreSample/tree/master/src/OptionsSample 。
總結
本章介紹了 IOptionsMonitor
的實現:通過 IConfiguration 所提供的 ChangeToken
,來注冊監聽事件,對其 CurrentValue
進行更新。到此,ASP.NET Core 中的 Options 源碼也就分析完了,其本身比較簡單,並沒有太多東西。更具體的可以去 Github 上看完整的源碼。