“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. ArrayList、Vactor以及LinkList的区别

    ArrayList:底层数据结构是数组结构,你就可以把它看成是一个可变大小的且只能装对象的数组.因为数组有索引(角标)所以ArrayList的查询速度快,而添加删除元素速度稍慢.因为,你每删除或者添加 ...

  2. n条直线的最多交点

    #include <iostream>using namespace std;int main(){int i,n;while(cin>>n){if(n==0||n==1) c ...

  3. jquery append 动态添加的元素事件on 不起作用的解决方案

    用jquery添加新元素很容易,面对jquery append 动态添加的元素事件on 不起作用我们该如何解决呢?on方法中要先找到原选择器(如例.info),再找到动态添加的选择器(如列.delet ...

  4. jquery精简选项卡

    <!DOCTYPE html><html><head lang="en">    <meta charset="UTF-8&qu ...

  5. a 标签的四个伪类

    link        有链接属性时visited    链接地址已被访问过active     被用户激活(在鼠标点击与释放之间发生的事件)hover      其鼠标悬停 <!DOCTYPE ...

  6. 移动端h5摇一摇事件

    // 摇一摇动作 //获取加速度信息 //通过监听上一步获取到的x, y, z 值在一定时间范围内的变化率,进行设备是否有进行晃动的判断. //而为了防止正常移动的误判,需要给该变化率设置一个合适的临 ...

  7. ASP.NET MVC 中使用 UEditor 富文本编辑器

    在上篇<使用ASP.NET MVC+Entity Framework快速搭建博客系统>中,已经基本上可以实现博客分类和博客文章的CURD.但是,文章编辑界面实在是…… 好吧,咱得搞专业点. ...

  8. Delphi RichEdit的内容保存为图片

    uses RichEdit; {将RichEdit1的内容保存为图片,此函数也适合于RxRichEdit,即RichEdit: TRxRichEdit}procedure RichEditToCanv ...

  9. 剖析magento中关于Email模板的设置

    public function send()    {        $emailTemplate = Mage::getModel('core/email_template');        // ...

  10. ASP.NET MVC 中将FormCollection与实体间转换方法【转】

    将Action动作中传递的FormCollection转变成对应的实体,可以使用Controller的TryUpdateModel()方法. 示例如下: [HttpPost] public Actio ...