ASP.NET Core 2.1 源碼學習之 Options[3]:IOptionsMonitor


前面我們講到 IOptionsIOptionsSnapshot,他們兩個最大的區別便是前者注冊的是單例模式,后者注冊的是 Scope 模式。而 IOptionsMonitor 則要求配置源必須是可監聽的,用來實現 Options 實例的自動更新,並對外提供了 OnChage 事件,給我們更多的控制權。

目錄

  1. IOptionsMonitor
  2. OptionsMonitor源碼探索
  3. ConfigurationChangeTokenSource
  4. 示例

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 上看完整的源碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM