“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<TOptions>的服务对象,后者的Value通过配置绑定生成的Options对象。为了能够得到所需的服务对象,它借助两个扩展方法AddOptions和Configure<TOptions>注册了必要的服务。

   1: IConfiguration config = ...;
   2: FormatOptions options = new ServiceCollection()
   3:     .AddOptions()
   4:     .Configure<FormatOptions>(config.GetSection("Format"))
   5:     .BuildServiceProvider()
   6:     .GetService<IOptions<FormatOptions>>()
   7:     .Value;

二、扩展方法AddOptions

依然Options对象最终是利用依赖注入的方式创建的一个类型为IOptions<TOptions>的服务对象得到的,我们就先来认识一下这个接口。这是一个泛型接口,泛型参数类型TOptions代码的正式Options对象对应的类型。IOptions<TOptions>接口的定义如下,它只有一个唯一的只读属性Value返回我们所需的Options对象。

   1: public interface IOptions<out TOptions> where TOptions: class, new()
   2: {
   3:     TOptions Value { get; }
   4: }

当我们调用ServiceCollection的AddOptions的时候,该方法仅仅是按照如下的方式针对该类型注册了一个服务而已,这个服务的真实类型为OptionsManager <TOptions> ,注册的服务采用的生命周期模式为Singleton。换句话说,配置绑定生成的Options对象最终返回的实际上是通过OptionsManager <TOptions> 创建的。

   1: public static IServiceCollection AddOptions(this IServiceCollection services)
   2: {
   3:     services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
   4:     return services;
   5: }

如下所示的是 OptionsManager <TOptions> 类型的定义,我们可以看到它的构造函数接受一个元素类型为IConfigureOptions<TOptions>的集合作为参数,我们将实现了该接口的类型以及对应对象统称为ConfigureOptions<TOptions>。IConfigureOptions<TOptions>接口定义了一个唯一的Configure方法,该方法将一个Options对象作为输入参数。从定义可以看出一个ConfigureOptions<TOptions>对象的作用与一个类型为Action<TOptions>的委托对象,所以对于它的实现类型ConfigureOptions<TOptions>来说,对应的对象就直接通过一个Action<TOptions>对象来创建。

   1: public class OptionsManager<TOptions> : IOptions<TOptions> where TOptions: class, new()
   2: {
   3:     public OptionsManager(IEnumerable<IConfigureOptions<TOptions>> 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<TOptions>: IConfigureOptions<TOptions> where TOptions : class, new()
  13: {
  14:     public Action<TOptions> Action { get; private set; }
  15:     public ConfigureOptions(Action<TOptions> action)
  16:     {
  17:         this.Action = action;
  18:     }
  19:     public void Configure(TOptions options)
  20:     {
  21:         this.Action(options);
  22:     }
  23: }

Options对象的创建体现在 OptionsManager <TOptions>类型的Value属性上。该属性的实现非常简单,它先调用默认无参构造函数(Options类型必须具有一个默认无参构造函数)创建一个空的Options对象,在返回之前,它会将其递交给初始化时指定的ConfigureOptions<TOptions>对象进行逐个处理。毫无疑问,针对Bind方法的调用肯定是通过某个ConfigureOptions<TOptions>对象参与到整个流程之中的,具体的实现自然与另一个扩展方法Configure有关。

三、扩展方法Configure

Options模式仅仅涉及到针对ServiceCollection的两个扩展方法(AddOptions和Configure<TOptions>),前者将服务IOptions<TOptions>/ OptionsManager <TOptions>注册到ServiceCollection之上,后者又作了怎样的服务注册呢?

   1: public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config) where TOptions: class
   2: {
   3:     return services.AddSingleton<IConfigureOptions<TOptions>>( new ConfigureFromConfigurationOptions<TOptions>(config));
   4: }
   5:  
   6: public class ConfigureFromConfigurationOptions<TOptions> :ConfigureOptions<TOptions> where TOptions : class
   7: {
   8:     public ConfigureFromConfigurationOptions(IConfiguration config) 
   9:      : base(options => config.Bind(options))
  10:     { }
  11: }

从上面的代码片段可以看出,当我们调用ServiceCollection的扩展方法Configure<TOptions>时,该方法会利用指定 的Configuration对象创建一个ConfigureFromConfigurationOptions对象,并以服务类型IConfigureOptions<TOptions>注册到ServiceCollection上,采用的生命周期模式为Singleton。至于类型ConfigureFromConfigurationOptions,它是上面介绍的ConfigureOptions<TOptions>类型的继承者,创建该对象指定的Action<TOptions>委托对象通过调用Configuration对象的扩展方法Bind最终实现了配置绑定。

四、Options对象的创建

Options编程模式的背后以两个注册到ServiceCollection的服务为核心,这两个服务对应的服务接口分别是IOptions<TOptions>和IConfigureOptions<TOptions>,前者直接提供最终绑定了配置数据的Options对象,后者则在Options对象返回之前对它实施相应的初始化工作。这个两个服务分别通过扩展方法AddOptions和Configure方法注册到指定的ServiceCollection之中,服务的真实类型分别是OptionsManager<TOptions>和ConfigureFromConfigurationOptions<TOptions>,后者派生于ConfigureOptions<TOptions>。下图所示的UML体现了Options模型中涉及的这些接口/类型以及它们之间的关系。

作者:蒋金楠 
微信公众账号:大内老A
微博:www.weibo.com/artech

“Options模式”下的配置是如何绑定为Options对象的更多相关文章

  1. .NET Core采用的全新配置系统[3]: “Options模式”下的配置是如何绑定为Options对象

    配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置.值得推荐的做法就是采用<.NET Core采用的全新配置系统[1]: 读取 ...

  2. .NET Core采用的全新配置系统[4]: “Options模式”下各种类型的Options对象是如何绑定的?

    旨在生成Options对象的配置绑定实现在IConfiguration接口的扩展方法Bind上.配置绑定的目标类型可以是一个简单的基元类型,也可以是一个自定义数据类型,还可以是一个数组.集合或者字典类 ...

  3. “Options模式”下各种类型的Options对象是如何绑定的?

    “Options模式”下各种类型的Options对象是如何绑定的? 旨在生成Options对象的配置绑定实现在IConfiguration接口的扩展方法Bind上.配置绑定的目标类型可以是一个简单的基 ...

  4. Azure ARM模式下VNet配置中需要注意的几点事项

    虚拟网络的配置是所有公有云中非常重要的环节.把虚拟网络配置好,对整个系统的管理.维护,以及安全性都非常重要. 本文将介绍Azure在ARM模式下VNet配置中需要特别注意的几点. 一 Azure的VN ...

  5. Vmware在NAT模式下网络配置详解

    Vmware在NAT模式下网络配置详解 Linux中的网络配置对于接触Linux不久的小白菜来说,还是小有难度的,可能是不熟悉这种与windows系列迥然不同的命令行操作,也可能是由于对Linux的结 ...

  6. MVVM模式下 DataTemplate 中控件的绑定

    今天给ListBox中通过DataTemplate生成的Button绑定命令时,一开始Button始终找不到绑定的命令.现找到了正确的绑定方式,特来记录一下. 先上个正确的示例: <ListBo ...

  7. 微软URLRewriter.dll的url重写在目标框架.Net Framework2.0、4.0和应用程序池经典模式、集成模式下的配置

    大家参考几篇园子里面的这篇文章: 文章1:微软URLRewriter.dll的url重写的简单使用 (讲解了使用UrlReWriter.dll的下载.web.config如何在目标框架2.0应用程序池 ...

  8. Hadoop不同模式下关键配置属性

    Hadoop分为三种模式: 独立(或本地)模式. 伪分布模式. 全分布模式 不同模式下关键配置项及属性内容如下面表格所示 组件名称 配置的文件名 属性名称 独立模式 伪分布模式 全分布模式 Commo ...

  9. MVC模式下unity配置,报错“No connection string named '**Context' could be found in the application config file”

     写在前面: 第一次配置时好好的,后来第二次改到MVC模式,把依赖注入写成字典的单例模式时,由于新建的ORM(数据库映射模型EF),怎么弄都不用,一直报错"No connection str ...

随机推荐

  1. 对FineU框架Grid多表头合计行导出Excel的回顾

    年前用FineUI开发遇到了这样一个问题,Grid多表头合计行不能导出,后面到官方示例找了一下,庆幸的是找到了多表头的导出示例.然后当时为了省事,直接就复制粘贴完事,也没有仔细的研究代码.后来运行一看 ...

  2. Qt5 多显示器获取不同显示器的分辨率和位置的方法

    Qt5 多显示器获取不同显示器的分辨率和位置的方法 先放官方文档链接:QDesktopWidget - Qt5 Reference 之前一直在用被我乱搞后的ShadowPlayer作为默认播放器,后来 ...

  3. Facebook发布C++ HTTP框架Proxygen

    Facebook 宣布发布C++ HTTP 框架 Proxygen,其中包括了一个 HTTP server.Proxygen 是 oxygen 的谐音,支持 SPDY/3 和 SPDY/3.1,未来还 ...

  4. JUnit报空指针错误,控制台不报任何错误

    解决方法:1. 在测试类的beforeClass方法上加try-catch块 2. 添加main方法,里面添加beforeClass();

  5. hibernate中先建表还是先建实体类

    在实际工作中往往是先建表然后再生成类原因:建好数据库表之后往往要对数据表进行一些优化,比如说建索引,比如说建中间表,比如建视图.如果先建类的话这些优化是无法生成的

  6. apache配置重写

    linux环境下 <IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-d Rewrite ...

  7. Oracle 导出HTML

    http://www.linuxidc.com/Linux/2010-10/29133.htm Oracle 执行计划: http://czmmiao.iteye.com/blog/1471756 h ...

  8. WPS Office手机版调用接口代码指导帖之一(Android)

    经常会有一些喜欢开发鼓捣的童鞋问我们,WPS Office手机版是否提供调用接口,希望在android中使用一个调用命令,直接调用WPS手机版来打开指定的DOC文件,而不用弹出一个程序可选列表(如果用 ...

  9. chapter 10 统计检验

    1.permutation test 用途:用于检验两组数据是否出生于同一分布 思路:如果产生于同一分布,两组数据混合,重新排列后,计算的基于两组数据的函数值(均值,中位数,方差等,下面程序中使用f指 ...

  10. Spring、实例化Bean的三种方法

    1.使用类构造器进行实例化 <bean id="personIService" class="cn.server.impl.PersonServiceImpl&qu ...