ASP.NET Core的配置(3): 将配置绑定为对象[下篇]

我们在《读取配置信息》通过实例的形式演示了如何利用Options模型以依赖注入的方式直接获取由指定配置节绑定生成的Options对象,我们再次回顾一下当初我们编写的程序。如下面的代码片段所示,基于Options模型的配置绑定的编程基本采用这样的模式:先后调用ServiceCollection的扩展方法AddOption和Configure注册Options模型相关的服务并完成Options类型与指定配置节之间的映射,然后利用由此生成ServiceProvider获得一个类型为IOptions<TOptions>的服务示例,后者的Value就是配置绑定生成的Options对象。

   1: public static class ServiceCollectionExtensions
   2: {
   3:     public static IServiceCollection AddOptions(this IServiceCollection services)
   4:     {
   5:         return services.AddSingleton(typeof(IOptions<>), typeof(OptionsManager<>));
   6:     }
   7: }
   8:  
   9: public interface IOptions<TOptions> where TOptions:class, new()
  10: {
  11:     TOptions Value { get; }
  12: }

一、IOptions <TOptions>

由于Options模型的编程仅仅涉及到上述几个方法的调用,所以只要搞清楚这几个方法背后的实现逻辑,我们也就彻底了解了Options模型的实现原理。首先当我们调用ServiceCollection的扩展方法时,实际上仅仅是按照如下的方式注册了一个针对IOptions <TOptions>接口类型的服务而已。服务接口IOptions<TOptions>仅仅定义了一个只读属性Value,该属性返回的正是绑定了指定配置数据的Options对象。

   1: public class OptionsManager<TOptions> : IOptions<TOptions> where TOptions : class, new()
   2: {
   3:     private Lazy<TOptions> optionsAccessor;
   4:     public OptionsManager(IEnumerable<IConfigureOptions<TOptions>> setups)
   5:     {
   6:         optionsAccessor = new Lazy<TOptions>(() =>
   7:         {
   8:             TOptions options = new TOptions();
   9:             setups.ForEach(it => it.Configure(options));
  10:             return options;
  11:         });
  12:     }
  13:     public TOptions Value
  14:     {
  15:         get { return optionsAccessor.Value; }
  16:     }
  17: }

通过上面的给出的代码片段我们不难看出,AddOptions方法实际上是以Singleton模式注册了一个类型为OptionsManager<TOptions>的服务,如下所示的代码片段基本反映了该类型的实现逻辑。如下面的代码片段所示,OptionsManager<TOptions>的只读属性Value返回的Options对象是以“延迟加载(Lazy Loading)”的形式被提供。Options对象创建的逻辑也很简单,我们直接调用其默认构造函数创建一个空的Options对象,然后将其递交给在构造函数中指定的一系列IConfigureOptions<TOptions>进行设置,配置绑定就这这个过程中完成。

   1: public class OptionsManager<TOptions> : IOptions<TOptions> where TOptions : class, new()
   2: {
   3:     private Lazy<TOptions> optionsAccessor;
   4:     public OptionsManager(IEnumerable<IConfigureOptions<TOptions>> setups)
   5:     {
   6:         optionsAccessor = new Lazy<TOptions>(() =>
   7:         {
   8:             TOptions options = new TOptions();
   9:             setups.ForEach(it => it.Configure(options));
  10:             return options;
  11:         });
  12:     }
  13:     public TOptions Value
  14:     {
  15:         get { return optionsAccessor.Value; }
  16:     }
  17: }

二、IConfigureOptions<TOptions>

IConfigureOptions<TOptions>接口抽象了针对Options对象的配置行为,这个行为体现在定义其中的Configure方法。ConfigureOptions<TOptions>实现了这个接口,它采用在构造函数提供的Action<TOptions>完成对Options对象的配置。

   1: public interface IConfigureOptions<TOptions> where TOptions : class, new()
   2: {
   3:     void Configure(TOptions options);
   4: }
   5:  
   6: public class ConfigureOptions<TOptions> : IConfigureOptions<TOptions> where TOptions : class, new()
   7: {
   8:     public Action<TOptions> Action { get; private set; }
   9:     public ConfigureOptions(Action<TOptions> action)
  10:     {
  11:         this.Action = action;
  12:     }
  13:     public void Configure(TOptions options)
  14:     {
  15:         this.Action(options);
  16:     }
  17: }

针对Options对象的配置绑定工作实现在一个名为ConfigureFromConfigurationOptions<TOptions>的类中。如下面的代码片段所示,这个类型直接继承ConfigureOptions<TOptions>,在构造函数中指定的Configuration对象承载了最终需要绑定到Options对象上的配置数据,它直接调用Configuration对象的扩展方法Bind完成了针对Options对象的配置绑定。

   1: public class ConfigureFromConfigurationOptions<TOptions>: ConfigureOptions<TOptions> where TOptions : class, new()
   2: {
   3:     public ConfigureFromConfigurationOptions(IConfiguration configuration) : base(options => configuration.Bind(options))
   4:     { }
   5: }

在Options模型中,ConfigureFromConfigurationOptions<TOptions>对象通过扩展方法Configure方法被注册到指定的ServiceCollection对象中。如下面的代码片段所示,Configure方法直接利用作为参数传入的Configuration对象创建一个ConfigureFromConfigurationOptions<TOptions>对象,并将这个对象注册到ServiceCollection之中。

   1: public static class ServiceCollectionExtensions
   2: {
   3:     public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration configuration) 
   4:     where TOptions : class, new()
   5:     {
   6:         return services.AddInstance<IConfigureOptions<TOptions>>(new ConfigureFromConfigurationOptions<TOptions>(configuration));
   7:     }
   8: }

三、Options对象的提供

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

对于一个包含服务注册描述信息的ServiceCollection,当我们分别调用其扩展方法AddOptions和Configure完成了相应的服务注册之后,我们就可以利用由它生成的ServiceProvider对象来提供针对接口类型IOptions <TOptions>的服务实例,并通过后者的只读属性Value得到配置绑定生成的Options对象。

ServiceProvider提供的这个服务实例自然是一个OptionsManager<TOptions>对象,当ServiceProvider调用构造函数对它进行实例化的时候,我们注册的ConfigureFromConfigurationOptions<TOptions>对象会以构造器注入的形式作为参数。在构造函数执行过程中,一个空的Options对象先被创建出来后会作为参数调用ConfigureFromConfigurationOptions<TOptions>的Configure方法,后者将在预先指定的Configuration对象绑定到这个Options对象之上。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AILearning

多线程的基础学习

进程:是一个正在执行中的程序, 每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。 线程:是进程中的一个独立的控制单元, 线程在控制中进...

19470
来自专栏Ryan Miao

java线程(1)--概念基础

参考:http://lavasoft.blog.51cto.com/62575/99150 http://blog.csdn.net/baby_newstar/...

34680
来自专栏轻量级微服务

微服务下跨语言 RPC 实现

目前主流的 Java 开发框架 Spring Boot,为了更方便集成 gRPC,自己开发了 spring-boot-starter-grpc,仅需简单的几行配...

29430
来自专栏企鹅号快讯

Python的进程

进程 说明:本文是基于Py2.X环境, Python实现多进程的方式主要有两种:一种方法是使用os模块中的fork方法; 另一种是使用multiprocessi...

272100
来自专栏菩提树下的杨过

bash/shell编程学习(2)

注:linux中有一个经典名言【一切皆文件】,/dev/null可以认为是一个特殊的空文件,更形象点,可以理解为科幻片中的黑洞,任何信息重向定输出到它后,便有去...

8930
来自专栏Python绿色通道

Python的进程

Python实现多进程的方式主要有两种:一种方法是使用os模块中的fork方法; 另一种是使用multiprocessing模块。这两种方法的区别在于前者仅适用...

12620
来自专栏数据结构与算法

2991:2011

2991:2011 查看 提交 统计 提问 总时间限制:1000ms内存限制:65536kB描述已知长度最大为200位的正整数n,请求出2011^n的后四位。输...

34870
来自专栏郭耀华‘s Blog

Python 中的 if __name__ == '__main__' 该如何理解

12430
来自专栏小二的折腾日记

day5(面向对象2)

wait notify notifyAll 都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,以为只有同步才具有锁。 为什么这些操作线程...

6110
来自专栏林冠宏的技术文章

Golang 的 协程调度机制 与 GOMAXPROCS 性能调优

Golang 简称 Go,Go 的协程(goroutine) 和我们常见的线程(Thread)一样,拥有其调度器。

47810

扫码关注云+社区

领取腾讯云代金券