選項用來提供對相關設置的強類型訪問,讀取配置首選使用選項模式。選項無法脫離容器使用,依賴容器,實現了選項不同的訪問方式。選項模式使用了泛型包裝器,因此具備瞭如下優點: 不需要顯示註冊選項具體類型,只需要將泛型包裝器註入到容器中; 對於選項實例的評估推遲到獲取IOptions.Value時進行,而不是 ...
選項用來提供對相關設置的強類型訪問,讀取配置首選使用選項模式。選項無法脫離容器使用,依賴容器,實現了選項不同的訪問方式。選項模式使用了泛型包裝器,因此具備瞭如下優點:
- 不需要顯示註冊選項具體類型,只需要將泛型包裝器註入到容器中;
- 對於選項實例的評估推遲到獲取IOptions.Value時進行,而不是在註入時進行,這樣就可以獲取不同生命周期的選項;
- 可以對選項進行泛型約束;
選項註入
選項模式向容器中註入了三種類型的選項泛型包裝器:IOptions<>,IOptionsSnapshot<>,IOptionsMonitor<>。其中IOptionsSnapshot<>被註冊為Scoped。註入了IOptionsFactory<>泛型選項工廠,用來創建選項實例。註入了IOptionsMonitorCache<>,由IOptionsMonitor<>用於緩存泛型實例。
public static IServiceCollection AddOptions(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(UnnamedOptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
return services;
}
選項不同生命周期
不同類型的選項介面功能不同:
- IOptions<>:實現類為UnnamedOptionsManager,其中TOptions欄位保存選項實例(不支持命名選項)。在首次獲取Value時,會調用工廠創建選項實例。因為被註冊為單例,因此無法識別配置修改。
- IOptionsSnapshot<>:實現類為OptionsManager,其中OptionsCache欄位(私有,非容器獲取)保存選項實例(支持命名選項)。被註冊為範圍,在範圍內首次獲取Value時,會調用工廠創建選項實例,並將其保存到私有的OptionsCache中,在範圍內選項值不變,不同範圍內選項值根據獲取時配置文件的不同而不同。
- IOptionsMonitor<>:實現類為OptionsMonitor,其中IOptionsMonitorCache欄位,該欄位的值是從容器中解析的,用來緩存選項實例。OptionsMonitor還註入了IOptionsChangeTokenSource列表,可以監聽配置源的修改。當監聽到修改時,調用工廠重新創建選項以刷新選項值。
internal sealed class UnnamedOptionsManager<[DynamicallyAccessedMembers(
Options.DynamicallyAccessedMembers)] TOptions> : IOptions<TOptions>
where TOptions : class
{
private readonly IOptionsFactory<TOptions> _factory;
private volatile object _syncObj;
private volatile TOptions _value;
public UnnamedOptionsManager(IOptionsFactory<TOptions> factory) => _factory = factory;
public TOptions Value
{
get
{
if (_value is TOptions value)
return value;
lock (_syncObj ?? Interlocked.CompareExchange(ref _syncObj, new object(), null) ?? _syncObj)
{
return _value ??= _factory.Create(Options.DefaultName);
}
}
}
}
public class OptionsManager<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions>
: IOptions<TOptions>, IOptionsSnapshot<TOptions>
where TOptions : class
{
private readonly IOptionsFactory<TOptions> _factory;
private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>();
public OptionsManager(IOptionsFactory<TOptions> factory)
{
_factory = factory;
}
public TOptions Value => Get(Options.DefaultName);
public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
if (!_cache.TryGetValue(name, out TOptions options))
{
IOptionsFactory<TOptions> localFactory = _factory;
string localName = name;
options = _cache.GetOrAdd(name, () => localFactory.Create(localName));
}
return options;
}
}
public class OptionsMonitor<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions>
: IOptionsMonitor<TOptions>, IDisposable
where TOptions : class
{
private readonly IOptionsMonitorCache<TOptions> _cache;
private readonly IOptionsFactory<TOptions> _factory;
private readonly List<IDisposable> _registrations = new List<IDisposable>();
internal event Action<TOptions, string> _onChange;
public OptionsMonitor(IOptionsFactory<TOptions> factory,
IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
{
_factory = factory;
_cache = cache;
void RegisterSource(IOptionsChangeTokenSource<TOptions> source)
{
IDisposable registration = ChangeToken.OnChange(
() => source.GetChangeToken(),
(name) => InvokeChanged(name),
source.Name);
_registrations.Add(registration);
}
// 此處簡寫
foreach (IOptionsChangeTokenSource<TOptions> source in sources)
{
RegisterSource(source);
}
}
private void InvokeChanged(string name)
{
name = name ?? Options.DefaultName;
_cache.TryRemove(name);
TOptions options = Get(name);
if (_onChange != null)
_onChange.Invoke(options, name);
}
public TOptions CurrentValue
{
get => 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)
{
var disposable = new ChangeTrackerDisposable(this, listener);
_onChange += disposable.OnChange;
return disposable;
}
public void Dispose()
{
foreach (IDisposable registration in _registrations)
{
registration.Dispose();
}
_registrations.Clear();
}
}
選項配置
選項模式提供了IConfigureOptions<>、IConfigureNamedOptions<>和IPostConfigureOptions<>介面,用來對選項進行配置。IConfigureNamedOptions<>繼承了IConfigureOptions<>介面,增加了命名選項配置功能。這三個介面中都有一個對選項配置的方法,將介面註入到容器中,當調用工廠創建選項時,會調用介面中的配置方法對選項進行配置。首先會調用IConfigureOptions<>、IConfigureNamedOptions<>介面中的配置方法,然後調用IPostConfigureOptions<>介面中的配置方法。
// OptionsFactory<>
public TOptions Create(string name)
{
TOptions options = CreateInstance(name);
foreach (IConfigureOptions<TOptions> setup in _setups)
{
if (setup is IConfigureNamedOptions<TOptions> namedSetup)
namedSetup.Configure(name, options);
else if (name == Options.DefaultName)
setup.Configure(options);
}
foreach (IPostConfigureOptions<TOptions> post in _postConfigures)
{
post.PostConfigure(name, options);
}
// 選項驗證...
return options;
}
protected virtual TOptions CreateInstance(string name)
{
return Activator.CreateInstance<TOptions>();
}
選項驗證
選項模式提供了IValidateOptions<>介面,包含一個Validate方法對選項進行驗證。將介面註入容器中,當調用工廠創建選項時,會調用介面中的Validate方法對選項進行驗證。
// OptionsFactory<>
public TOptions Create(string name)
{
TOptions options = CreateInstance(name);
// 選項配置...
if (_validations.Length > 0)
{
var failures = new List<string>();
foreach (IValidateOptions<TOptions> validate in _validations)
{
ValidateOptionsResult result = validate.Validate(name, options);
if (result is not null && result.Failed)
failures.AddRange(result.Failures);
}
if (failures.Count > 0)
throw new OptionsValidationException(name, typeof(TOptions), failures);
}
return options;
}
如果需要為選項添加驗證,實現IValidateOptions<>介面並註入到容器即可:
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton
<IValidateOptions<SettingsOptions>, ValidateSettingsOptions>());
也可以添加DataAnnotations驗證,調用ValidateDataAnnotations擴展方法即可,該方法定義在 Microsoft.Extensions.Options.DataAnnotations中。需要先調用AddOptions<>擴展方法創建OptionsBuilder<>:
builder.Services.AddOptions<SettingsOptions>()
.Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
.ValidateDataAnnotations();
綁定配置
通過如下擴展方法將選項綁定到配置:
builder.Services.Configure<SettingsOptions>(builder.Configuration.GetSection("Settings"));
綁定到配置是通過IConfiguration.Bind()擴展方法實現的,同時,也添加了對配置修改的監聽:
public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] 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));
}
轉載請註明出處,歡迎交流。