“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. androik_sdk 更新慢问题解决办法。

    在windows中更改hosts(找到文件夹C:\Windows\System32\drivers\etc找到里面的hosts文件 )在这里添加74.125.237.1 dl-ssl.google.c ...

  2. NHibernate各种数据库连接参数文件配置方法说明

    //NHibernate各种数据库连接参数文件配置方法说明 //配置文件Config/Hibernate.cfg.xml内容如下所示: <?xml version="1.0" ...

  3. 轻松解决ubuntu系统引导问题

    什么是ppa PPA,表示 Personal Package Archives,也就是个人软件包集. 有很多软件因为种种原因,不能进入官方的 Ubuntu 软件仓库. 为了方便 Ubuntu 用户使用 ...

  4. 关于google CDN 在中国访问不了的解决办法

    因原网站的script部分使用了google CDN,导致在中国看不了跟google相关的所有东西 解决方法: 得把google CDN 连接改成其他公司的CDN 例: 修改前:<script ...

  5. Django建立helloworld自定义页面

    目录介绍 按照上一篇初学Django首先建立起一个默认站点,生成如下的目录结构: manage.py 一种命令行工具,允许你以多种方式与该 Django 项目进行交互. 键入python manage ...

  6. struts2笔记09-动态方法调用

    1.action配置 <action name="testDynamic" class="com.test.actions.TestAction"> ...

  7. (1)html初步--表格的使用

    用表格标签制作简历 html代码: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" " ...

  8. C语言的本质(6)——位运算

    位运算是指按二进制进行的运算.在系统软件中,常常需要处理二进制位的问题.C语言提供了6个位操作运算符.这些运算符只能用于整型操作数,即只能用于带符号或无符号的char,short,int与long类型 ...

  9. 网易云课堂_程序设计入门-C语言_第六章:数组_2鞍点

    2 鞍点(5分) 题目内容: 给定一个n*n矩阵A.矩阵A的鞍点是一个位置(i,j),在该位置上的元素是第i行上的最大数,第j列上的最小数.一个矩阵A也可能没有鞍点. 你的任务是找出A的鞍点. 输入格 ...

  10. Train Problem II(卡特兰数+大数乘除)

    Train Problem II Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...