ASP.NET Core 2.1 源碼學習之 Options[2]:IOptions


上一章 中,介紹了Options的注冊,而在使用時只需要注入 IOption<T> 即可:

public ValuesController(IOptions<MyOptions> options)
{
    var opt = options.Value;
}

本章就來詳細介紹一下我們最熟悉的IOptions對象。

目錄

  1. IOptions
  2. OptionsManager
  3. OptionsFactory
  4. OptionsCache
  5. IOptionsSnapshot

IOptions

IOptions 定義非常簡單,只有一個Value屬性:

public interface IOptions<out TOptions> where TOptions : class, new()
{
    TOptions Value { get; }
}

上一章 中,我們知道它的默認實現為OptionsManager

    services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));

OptionsManager

OptionsManager 對options的創建和獲取進行管理,我們先看一下它的源碼:

public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
{
    private readonly IOptionsFactory<TOptions> _factory;
    private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>(); // Note: this is a private cache

    public OptionsManager(IOptionsFactory<TOptions> factory)
    {
        _factory = factory;
    }

    public TOptions Value => Get(Options.DefaultName);

    public virtual TOptions Get(string name) => _cache.GetOrAdd(name, () => _factory.Create(name ?? Options.DefaultName));
}

如上,OptionsManager 的實現非常簡單,使用IOptionsFactory來創建options對象,並使用內部屬性OptionsCache<TOptions> _cache進行緩存。

OptionsFactory

IOptionsFactory 只定義了一個Create方法:

public interface IOptionsFactory<TOptions> where TOptions : class, new()
{
    TOptions Create(string name);
}

其默認實現為:OptionsFactory

public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{
    private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
    private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;

    public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures)
    {
        _setups = setups;
        _postConfigures = postConfigures;
    }

    public TOptions Create(string name)
    {
        var options = new TOptions();
        foreach (var setup in _setups)
        {
            if (setup is IConfigureNamedOptions<TOptions> namedSetup)
            {
                namedSetup.Configure(name, options);
            }
            else if (name == Options.DefaultName)
            {
                setup.Configure(options);
            }
        }
        foreach (var post in _postConfigures)
        {
            post.PostConfigure(name, options);
        }
        return options;
    }
}

OptionsFactory的構造函數中注入了IConfigureOptions<>IPostConfigureOptions<>,也就是我們在 上一章 中介紹的 Configure 方法中所注冊的配置,而這里使用了 IEnumerable 類型,則表示當注冊多個時,為按順序依次執行。

而我們在第一章中遇到的疑惑也豁然開朗,其Create方法,依次調用完Configure方法后,再調用PostConfigure方法,完成配置項的創建。

OptionsCache

OptionsCache 則是對字典的一個簡單封裝,就不再多說,直接看代碼:

public class OptionsCache<TOptions> : IOptionsMonitorCache<TOptions> where TOptions : class
{
    private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal);

    public void Clear() => _cache.Clear();

    public virtual TOptions GetOrAdd(string name, Func<TOptions> createOptions)
    {
        name = name ?? Options.DefaultName;
        return _cache.GetOrAdd(name, new Lazy<TOptions>(createOptions)).Value;
    }

    public virtual bool TryAdd(string name, TOptions options)
    {
        name = name ?? Options.DefaultName;
        return _cache.TryAdd(name, new Lazy<TOptions>(() => options));
    }

    public virtual bool TryRemove(string name)
    {
        name = name ?? Options.DefaultName;
        return _cache.TryRemove(name, out var ignored);
    }
}

IOptionsSnapshot

最后再來介紹一下 IOptionsSnapshot :

public interface IOptionsSnapshot<out TOptions> : IOptions<TOptions> where TOptions : class, new()
{
    TOptions Get(string name);
}

看到Get方法的Name參數,我想大家便會想到在第一章中所介紹的指定NameConfigure方法,這便是它的用武之地了,通過Name的不同,可以配置同一個Options類型的多個實例。

IOptionsSnapshot的實現與IOptions一樣,都是OptionsManager

services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));

通過如上代碼,我們也可以發現它與IOptions還有一個本質的區別:IOptionsSnapshot注冊的是Scoped類型,每一個請求中,都會創建一個新的OptionsManager對象。這樣有什么好處呢?如果我們使用的是Action的方式配置的,那的確是沒什么用,但是如果是使用IConfiguration配置的,則在我們修改了配置文件時,重新創建的Options會保持一致。

總結

本文描述了在 .NET Core Options 系統中IOptions的使用及實現原理。由於IOptions使用的是單例模式,因此當配置發生變化時,我們無法獲取到最新的配置。而IOptionsSnapshot支持通過name來區分同一類型的不同 options ,並且每次請求都會重新創建 options 實例,相應的,會有稍微的性能損耗。如果我們希望能夠監控配置源的變化,來自動更新,則可以使用下一章要介紹的更為強大的 IOptionsMonitor


免責聲明!

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



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