前言
前面基本介紹了,官方對於asp .net core 設計配置和設計服務的框架的一些思路。看下服務和配置之間是如何聯絡的吧。
正文
服務:
public interface ISelfService
{
string ShowOptionName();
}
public class SelfService : ISelfService
{
IOptions<SelfServiceOption> _options;
public SelfService(IOptions<SelfServiceOption> options)
{
this._options = options;
}
public string ShowOptionName()
{
return _options.Value.Name;
}
}
實體配置類:
public class SelfServiceOption
{
public string Name { get; set; }
}
配置:
"SelfService": {
"name" : "zhangsan"
}
註冊:
services.AddSingleton<ISelfService, SelfService>();
services.Configure<SelfServiceOption>(Configuration.GetSection("SelfService"));
獲取呼叫在startup.Configure中:
var SelfService = app.ApplicationServices.GetService<ISelfService>();
Console.WriteLine(SelfService.ShowOptionName());
結果:
經過前面系列中,我們非常好的能夠理解:services.Configure
在反射通過屬性獲取值過程中就是SelfService+":"+屬性名,對字典進行獲取對應的值。
那麼看下為什麼我們在配置的時候需要IOptions
看下IOptions 的實現類OptionsManager:
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
/// <summary>
/// Initializes a new instance with the specified options configurations.
/// </summary>
/// <param name="factory">The factory to use to create options.</param>
public OptionsManager(IOptionsFactory<TOptions> factory)
{
_factory = factory;
}
/// <summary>
/// The default configured <typeparamref name="TOptions"/> instance, equivalent to Get(Options.DefaultName).
/// </summary>
public TOptions Value
{
get
{
return Get(Options.DefaultName);
}
}
/// <summary>
/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
/// </summary>
public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
// Store the options in our instance cache
return _cache.GetOrAdd(name, () => _factory.Create(name));
}
}
那麼我們呼叫Value 其實是呼叫IOptionsFactory的_factory.Create(name)。
檢視一下,IOptionsFactory的實現類OptionsFactory,看裡面的create方法。:
public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{
private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
private readonly IEnumerable<IValidateOptions<TOptions>> _validations;
/// <summary>
/// Initializes a new instance with the specified options configurations.
/// </summary>
/// <param name="setups">The configuration actions to run.</param>
/// <param name="postConfigures">The initialization actions to run.</param>
public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) : this(setups, postConfigures, validations: null)
{ }
/// <summary>
/// Initializes a new instance with the specified options configurations.
/// </summary>
/// <param name="setups">The configuration actions to run.</param>
/// <param name="postConfigures">The initialization actions to run.</param>
/// <param name="validations">The validations to run.</param>
public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<IValidateOptions<TOptions>> validations)
{
_setups = setups;
_postConfigures = postConfigures;
_validations = validations;
}
/// <summary>
/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
/// </summary>
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);
}
if (_validations != null)
{
var failures = new List<string>();
foreach (var validate in _validations)
{
var result = validate.Validate(name, options);
if (result.Failed)
{
failures.AddRange(result.Failures);
}
}
if (failures.Count > 0)
{
throw new OptionsValidationException(name, typeof(TOptions), failures);
}
}
return options;
}
}
切開三段看:
第一段
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);
}
}
上面是例項化我們的配置類。
namedSetup.Configure(name, options); 就是給我們selfServiceOption具體繫結。
這就要回到我們注入的地方了。
services.Configure<SelfServiceOption>(Configuration.GetSection("SelfService"));
來看下Configure 寫的是什麼,自己看具體的實現哈:
public static IServiceCollection Configure<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));
}
AddOptions方法 就是註冊具體實現的。
public static IServiceCollection AddOptions(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
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;
}
重點來看下NamedConfigureFromConfigurationOptions 這個東西:
public class NamedConfigureFromConfigurationOptions<TOptions> : ConfigureNamedOptions<TOptions>
where TOptions : class
{
/// <summary>
/// Constructor that takes the <see cref="IConfiguration"/> instance to bind against.
/// </summary>
/// <param name="name">The name of the options instance.</param>
/// <param name="config">The <see cref="IConfiguration"/> instance.</param>
public NamedConfigureFromConfigurationOptions(string name, IConfiguration config)
: this(name, config, _ => { })
{ }
/// <summary>
/// Constructor that takes the <see cref="IConfiguration"/> instance to bind against.
/// </summary>
/// <param name="name">The name of the options instance.</param>
/// <param name="config">The <see cref="IConfiguration"/> instance.</param>
/// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action<BinderOptions> configureBinder)
: base(name, options => config.Bind(options, configureBinder))
{
if (config == null)
{
throw new ArgumentNullException(nameof(config));
}
}
}
NamedConfigureFromConfigurationOptions 繼承ConfigureNamedOptions是不是很眼熟了。
看下我們第一段呼叫部分:
if (setup is IConfigureNamedOptions<TOptions> namedSetup)
{
namedSetup.Configure(name, options);
}
後面就會呼叫NamedConfigureFromConfigurationOptions的Configure。
看下NamedConfigureFromConfigurationOptions的例項化方法,傳遞方法的第二個引數,options => config.Bind(options, configureBinder);
這個bind 是不是特別的眼熟,就是用來實體類繫結配置的,如有疑問可以看下前面幾篇,講述了繫結過程。
那麼看下ConfigureNamedOptions的Configure(NamedConfigureFromConfigurationOptions繼承ConfigureNamedOptions) 做了什麼吧,我們來到ConfigureNamedOptions:
public Action<TOptions> Action { get; }
public ConfigureNamedOptions(string name, Action<TOptions> action)
{
Name = name;
Action = action;
}
public virtual void Configure(string name, TOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
// Null name is used to configure all named options.
if (Name == null || name == Name)
{
Action?.Invoke(options, Dependency);
}
}
這時候就會呼叫傳遞進來的Action:options => config.Bind(options, configureBinder),傳遞的這個options就是我們例項化的SelfServiceOption。
configureBinder 是一個配置哈,這是個配置的,下面第二個引數,如果沒穿預設是一個()=>{};
如果需要傳遞可以這麼寫:
services.Configure<SelfServiceOption>(Configuration.GetSection("SelfService"), BinderOptions =>
{
BinderOptions.BindNonPublicProperties = true;
});
是不是很眼熟,就是設定可以繫結私有屬性的選項。
這就把我們的SelfServiceOption 和 Configuration.GetSection("SelfService") 已經繫結了。
那麼來看第二段:
foreach (var post in _postConfigures)
{
post.PostConfigure(name, options);
}
這個是什麼呢? 這個是這樣的,比如說我們的現在獲取到了配置,得到了實體類SelfServiceOption,裡面的name 是zhangsan。
這個PostConfigure 可以幫我們做後續修改,比如說我獲取到了是zhangsan,但是呢,我想做一個判斷,如果name 是zhangsan,就加一個字尾,name= name+"_a";
大概就是一些後續操作。
services.AddOptions<SelfServiceOption>().Configure((options) =>
{
if (options.Name == "zhangsan")
{
options.Name = "zhangsan_a";
}
});
效果如下:
第三段
if (_validations != null)
{
var failures = new List<string>();
foreach (var validate in _validations)
{
var result = validate.Validate(name, options);
if (result.Failed)
{
failures.AddRange(result.Failures);
}
}
if (failures.Count > 0)
{
throw new OptionsValidationException(name, typeof(TOptions), failures);
}
}
這一段很顯然就是來驗證我們的配置是否符合規格。原始碼就不解釋了,實踐篇注重實踐。
看下怎麼用的吧。
services.AddOptions<SelfServiceOption>().Configure((options) =>
{
if (options.Name == "zhangsan")
{
options.Name = "zhangsan_a";
}
}).Validate(options =>
{
return options.Name != "zhangsan_a";
});
然後就會報錯。因為上面的邏輯是如果zhangsan,那麼我把名字改成zhangsan_a,然後驗證裡面寫名字不能等於zhangsan_a。
結
以上只是個人整理,如有錯誤,望請指點。
下一節,服務中的配置熱更新。