.NET Core采用的全新配置系统[3]: “Options模式”下的配置是如何绑定为Options对象
配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置。值得推荐的做法就是采用《.NET Core采用的全新配置系统[1]: 读取配置数据》最后演示的方式将相关的配置定义成一个Options类型,并采用与类型定义想匹配的结构来定义原始的配置,这样就能利用它们之间的映射关系将读取的配置数据绑定为Options对象,我们将这种编程模式称为“Options模式”。 [ 本文已经同步到《ASP.NET Core框架揭秘》之中]
目录
一、配置绑定
二、扩展方法AddOptions
三、扩展方法Configure
四、Options对象的创建
一、配置绑定
对于一个Options对象来说,如果我们将其数据成员(这里主要指属性成员)视为其子节点,那么一个Options对象同样具有树形层次化结构,这与通过Configuration对象表示的配置树在结构上并没有本质的区别。如果Options类型的数据成员定义与配置树结构具有匹配的结构,那么将后者绑定为一个对应类型的Options对象是一件很容易的事情,对于这种将一个Configuration对象绑定为对应Options对象的行为简称为“配置绑定”。
配置绑定让我们可以根据得到的Configuration对象生成相应的Options对象,相关的API定义在“Microsoft.Extensions.Configuration.Binder”这个NuGet包中,后者为IConfiguration接口定义了如下一个GetValue方法得到绑定生成的Options对象。在调用这个放过的时候,我们会创建一个空的Options对象并将其作为参数,该方法会将Configuration承载的配置数据绑定到Options对象上。
1: public static class ConfigurationBinder
2: {
3: public static void Bind(this IConfiguration configuration, object instance);
4: }
配置绑定的目标类型可以是一个简单的基元类型,也可以是一个自定义数据类型,还可以是一个数组、集合或者字典类型。上述的这个Bind方法在进行配置绑定的过程,针对不同的目标类型,它会采用不同的策略。至于该方法具体的实现原理,我们会在后续的部分予以单独介绍,而目前介绍的重点是Options模式采用的API在背后是如何调用这个方法得到所需的Options对象的。
我们在回顾一下《.NET Core采用的全新配置系统[1]: 读取配置数据》演示的采用Options模式读取配置的例子。Options模式是对依赖注入的应用,我们知道针对依赖注入的编程只涉及两个方面,即注册相应的服务到ServiceCollection对象上,在利用后者创建相应的ServiceProvider来提供我们所需的服务对象。如下面的代码片段所示,Options模式最终的目的是利用ServiceProvider得到一个类型为IOptions
1: IConfiguration config = ...;
2: FormatOptions options = new ServiceCollection()
3: .AddOptions()
4: .Configure(config.GetSection("Format"))
5: .BuildServiceProvider()
6: .GetService>()
7: .Value;
二、扩展方法AddOptions
依然Options对象最终是利用依赖注入的方式创建的一个类型为IOptions
1: public interface IOptions<out TOptions> where TOptions: class, new()
2: {
3: TOptions Value { get; }
4: }
当我们调用ServiceCollection的AddOptions的时候,该方法仅仅是按照如下的方式针对该类型注册了一个服务而已,这个服务的真实类型为OptionsManager
1: public static IServiceCollection AddOptions(this IServiceCollection services)
2: {
3: services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
4: return services;
5: }
如下所示的是 OptionsManager
1: public class OptionsManager: IOptions where TOptions: class, new()
2: {
3: public OptionsManager(IEnumerable> setups);
4: public virtual TOptions Value { get; }
5: }
6:
7: public interface IConfigureOptions<in TOptions> where TOptions: class
8: {
9: void Configure(TOptions options);
10: }
11:
12: public class ConfigureOptions: IConfigureOptions where TOptions : class, new()
13: {
14: public ActionAction { get; private set; }
15: public ConfigureOptions(Actionaction)
16: {
17: this.Action = action;
18: }
19: public void Configure(TOptions options)
20: {
21: this.Action(options);
22: }
23: }
Options对象的创建体现在 OptionsManager
三、扩展方法Configure
Options模式仅仅涉及到针对ServiceCollection的两个扩展方法(AddOptions和Configure
1: public static IServiceCollection Configure(this IServiceCollection services, IConfiguration config) where TOptions: class
2: {
3: return services.AddSingleton>( new ConfigureFromConfigurationOptions (config));
4: }
5:
6: public class ConfigureFromConfigurationOptions:ConfigureOptions where TOptions : class
7: {
8: public ConfigureFromConfigurationOptions(IConfiguration config)
9: : base(options => config.Bind(options))
10: { }
11: }
从上面的代码片段可以看出,当我们调用ServiceCollection的扩展方法Configure
四、Options对象的创建
Options编程模式的背后以两个注册到ServiceCollection的服务为核心,这两个服务对应的服务接口分别是IOptions