在 上一章 中,介紹了Options
的注冊,而在使用時只需要注入 IOption<T>
即可:
public ValuesController(IOptions<MyOptions> options)
{
var opt = options.Value;
}
本章就來詳細介紹一下我們最熟悉的IOptions
對象。
目錄
- IOptions
- OptionsManager
- OptionsFactory
- OptionsCache
- 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
參數,我想大家便會想到在第一章中所介紹的指定Name
的Configure
方法,這便是它的用武之地了,通過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 。