配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置。值得推荐的做法就是采用《.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: voID Bind(this IConfiguration configuration,object instance);
4: }
配置绑定的目标类型可以是一个简单的基元类型,也可以是一个自定义数据类型,还可以是一个数组、集合或者字典类型。上述的这个Bind方法在进行配置绑定的过程,针对不同的目标类型,它会采用不同的策略。至于该方法具体的实现原理,我们会在后续的部分予以单独介绍,而目前介绍的重点是Options模式采用的API在背后是如何调用这个方法得到所需的Options对象的。
我们在回顾一下《.NET Core采用的全新配置系统[1]: 读取配置数据》演示的采用Options模式读取配置的例子。Options模式是对依赖注入的应用,我们知道针对依赖注入的编程只涉及两个方面,即注册相应的服务到ServiceCollection对象上,在利用后者创建相应的ServiceProvIDer来提供我们所需的服务对象。如下面的代码片段所示,Options模式最终的目的是利用ServiceProvIDer得到一个类型为IOptions<toptions>的服务对象,后者的Value通过配置绑定生成的Options对象。为了能够得到所需的服务对象,它借助两个扩展方法AddOptions和Configure<toptions>注册了必要的服务。
1: IConfiguration config = ...;3: .AddOptions()
5: .BuildServiceProvIDer()
6: .GetService<IOptions<Formatoptions>>()
7: .Value;
二、扩展方法AddOptions依然Options对象最终是利用依赖注入的方式创建的一个类型为IOptions<toptions>的服务对象得到的,我们就先来认识一下这个接口。这是一个泛型接口,泛型参数类型toptions代码的正式Options对象对应的类型。IOptions<toptions>接口的定义如下,它只有一个唯一的只读属性Value返回我们所需的Options对象。
interface IOptions<out toptions> where toptions: class,1)">new()4: }
当我们调用ServiceCollection的AddOptions的时候,该方法仅仅是按照如下的方式针对该类型注册了一个服务而已,这个服务的真实类型为OptionsManager <toptions> ,注册的服务采用的生命周期模式为Singleton。换句话说,配置绑定生成的Options对象最终返回的实际上是通过OptionsManager <toptions> 创建的。
static IServiceCollection AddOptions(this IServiceCollection services) 4: return services;
virtual toptions Value { get; }
6:
8: {
9: voID Configure(toptions options);
10: }
11:
@H_419_198@ 12: class ConfigureOptions<toptions>: IConfigureOptions<toptions> where toptions : 13: {
14: public Action<toptions> Action { get; private set; }
15: public ConfigureOptions(Action<toptions> action)
16: {
17: this.Action = action;
18: }
19: voID Configure(toptions options)
20: {
21: this.Action(options);
22: }
23: }
Options对象的创建体现在 OptionsManager <toptions>类型的Value属性上。该属性的实现非常简单,它先调用默认无参构造函数(Options类型必须具有一个默认无参构造函数)创建一个空的Options对象,在返回之前,它会将其递交给初始化时指定的ConfigureOptions<toptions>对象进行逐个处理。毫无疑问,针对Bind方法的调用肯定是通过某个ConfigureOptions<toptions>对象参与到整个流程之中的,具体的实现自然与另一个扩展方法Configure有关。
三、扩展方法ConfigureOptions模式仅仅涉及到针对ServiceCollection的两个扩展方法(AddOptions和Configure<toptions>),前者将服务IOptions<toptions>/ OptionsManager <toptions>注册到ServiceCollection之上,后者又作了怎样的服务注册呢?
static IServiceCollection Configure<toptions>(this IServiceCollection services,IConfiguration config) return services.AddSingleton<IConfigureOptions<toptions>>( new ConfigureFromConfigurationoptions<toptions>(config));5:
8: public ConfigureFromConfigurationoptions(IConfiguration config)
10: { }