前言

前面基本介紹了,官方對於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(Configuration.GetSection("SelfService"));

在反射通過屬性獲取值過程中就是SelfService+":"+屬性名,對字典進行獲取對應的值。

那麼看下為什麼我們在配置的時候需要IOptions options,也就是套一個Ioption呢?Ioption它是怎麼實現的呢?它的機制是什麼?

看下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。

以上只是個人整理,如有錯誤,望請指點。

下一節,服務中的配置熱更新。