旨在生成Options对象的配置绑定实现在IConfiguration接口的扩展方法Bind上。配置绑定的目标类型可以是一个简单的基元类型,也可以是一个自定义数据类型,还可以是一个数组、集合或者字典类型。通过前面的介绍我们知道ConfigurationProvider将原始的配置数据读取出来后会将其转成Key和Value均为字符串的数据字典,那么针对这些完全不同的目标类型,原始的配置数据如何通过数据字典的形式来体现呢? [ 本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、绑定简单数据类型
二、绑定复杂数据类型
三、绑定集合对象
四、绑定字典

一、绑定简单数据类型

我们先来说说针对简单数据类型的配置绑定。这里所谓的简单数据类型和复杂数据类型只有一个界定标准,那就是是否支持源自字符串类型的数据转换。也就是说,简单类型对象可以直接通过一个字符串转换而来,复杂类型对象则不能。如果目标类型是一个简单类型,在进行配置绑定的时候只需要将配置项的值(体现为ConfigurationSection的Value属性)转换成对应的数据类型就可以了。

对于简单类型的配置绑定,除了调用上述的扩展方法Bind来完成之外,我们其实还有更好的选择,那就是调用IConfiguration接口的另一个扩展方法GetValue。GetValue方法总是将一个原子配置项的值(字符串)转换成目标类型,所以我们在调用该方法是除了指定目标类型之外,还需要通过参数key指定这个原子配置项相对于当前Configuration对象的路径,也就是说参数key不仅仅可以指定为子配置项的Key(比如“Foo”),也可以设定为以下每个配置节相对于当前节点的路径(比如“Foo:Bar:Baz”)。如果指定的配置节没有值,或者配置节根本不存在,该方法会返回通过defaultValue参数指定的默认值。

   1: public static object GetValue(this IConfiguration configuration, Type type, string key, object defaultValue) ;

除了上述这个GetValue方法之外,IConfiguration接口还具有如下三个GetValue方法重载,它们最终都会调用上面这个方法来完成针对简单类型的配置绑定。前面两个方法以泛型参数的形式指定绑定的目标类型,如果没有显式指定默认值,意味着默认值为Null。

   1: public static T GetValue<T>(this IConfiguration configuration, string key);

   2: public static T GetValue<T>(this IConfiguration configuration, string key, T defaultValue);

在下面这段程序中,我们我们演示了针对三种功能数据类型的配置绑定。前面两种类型分别是Double和枚举,它们天生就是支持源自字符串的简单类型。第三种类型是我们自定义的表示二维坐标点的Point,由于我们通过应用TypeConverterAttribute特性为它注册了一个支持字符串转换的TypeConverter(PointTypeConverter),所示它也是一个简单类型。

   1: Dictionary<string, string> source = new Dictionary<string, string>

   2: {

   3:     ["foo"] = "3.14159265",

   4:     ["bar"] = "Female",

   5:     ["baz"] = "(1.1, 2.2)"

   6: };

   7:  

   8: IConfiguration config = new ConfigurationBuilder()

   9:     .Add(new MemoryConfigurationSource { InitialData = source })

  10:     .Build();

  11:  

  12: Debug.Assert(config.GetValue<double>("foo") == 3.14158265);

  13: Debug.Assert(config.GetValue<Gender>("bar") == Gender.Female);

  14: Debug.Assert(config.GetValue<Point>("baz").X == 1.1);

  15: Debug.Assert(config.GetValue<Point>("baz").Y == 2.2);

  16:  

  17: public enum Gender

  18: {

  19:     Male,

  20:     Female

  21: }

  22:  

  23: [TypeConverter(typeof(PointTypeConverter))]

  24: public class Point

  25: {

  26:     public double X { get; set; }

  27:     public double Y { get; set; }

  28:        

  29:  

  30: }

  31: public class PointTypeConverter : TypeConverter

  32: {

  33:     public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)

  34:     {

  35:         string[] split = value.ToString().Split(',');

  36:         double x = double.Parse(split[0].Trim().TrimStart('('));

  37:         double y = double.Parse(split[1].Trim().TrimEnd(')'));

  38:         return new Point { X = x, Y = y };

  39:     }

  40: }

二、绑定复杂数据类型

这里所谓的复杂类型表示一个具有属性数据成员的类型。如果通过一颗树来表示一个复杂对象,那么叶子节点承载所有的数据,并且叶子节点的数据类型均为简单类型。如果通过数据字典来提供一个复杂对象所有的原始数据,那么这个字典中只需要包含叶子节点对应的值即可。至于如何通过一个字典对象体现复杂对象的结构,我们只需要将叶子节点所在的路径作为字典元素的Key就可以了。

   1: public class Profile

   2: {

   3:     public Gender         Gender { get; set; }

   4:     public int            Age { get; set; }

   5:     public ContactInfo    ContactInfo { get; set; }

   6: }

   7:  

   8: public class ContactInfo

   9: {

  10:     public string EmailAddress { get; set; }

  11:     public string PhoneNo { get; set; }

  12: }

  13:  

  14: public enum Gender

  15: {

  16:     Male,

  17:     Female

  18: }

如上面的代码片段所示,我们定义了一个表示个人基本信息的Profile类,定义其中的三个属性(Gender、Age和ContactInfo)分别表示性别、年龄和联系方式。表示联系信息的ContactInfo对象具有两个属性(EmailAddress和PhoneNo)分别表示电子邮箱地址和电话号码。一个完整的Profile对象可以通过如下图所示的树来体现。

如果需要通过配置的形式来表示一个完整的Profile对象,我们只需要将四个叶子节点(性别、年龄、电子邮箱地址和电话号码)对应的数据定义在配置之中即可。对于承载配置数据的数据字典中,我们需要按照如下表所示的方式将这四个叶子节点的路径作为字典元素的Key。

Key

Value

Gender

Male

Age

18

ContactInfo:Email

foobar@outlook.com

ContactInfo:PhoneNo

123456789

如上面的代码片段所示,我们创建了一个ConfigurationBuilder对象并为之添加了一个MemoryConfigurationProvider,后者按照如表2所示的结构提供了原始的配置数据。我们完全按照Options编程模式将这些原始的配置属性绑定成一个Profile对象。

   1: Dictionary<string, string> source = new Dictionary<string, string>

   2: {

   3:     ["gender"]                       = "Male",

   4:     ["age"]                          = "18",

   5:     ["contactInfo:emailAddress"]     = "foobar@outlook.com",

   6:     ["contactInfo:phoneNo"]          = "123456789"

   7: };

   8:  

   9: IConfiguration config = new ConfigurationBuilder()

  10:     .Add(new MemoryConfigurationSource { InitialData = source })

  11:     .Build();

  12:  

  13: Profile profile = new ServiceCollection()

  14:     .AddOptions()

  15:     .Configure<Profile>(config)

  16:     .BuildServiceProvider()

  17:     .GetService<IOptions<Profile>>()

  18:     .Value;

三、绑定集合对象

这里所说的集合类型指的是实现了ICollection <T>接口的所有类型。如果将一个集合通过一棵树来表示,那么可以将集合元素作为集合对象自身的子节点。 比如一个Options对象是一个元素类型为Profile的集合,它对应的配置树具有如下图所示的结构。

对于如上图所示的这棵配置树,我们采用零基索引(以零开头的连续递增整数)来表示每个Profile对象在集合中的位置。实际上针对集合对象的配置树并无特别要求,它不要求作为索引的整数一定要从零开始(“1、2、3”这样的顺序也是可以得),也不要求它们一定具有连续性(“1、2、4”这样的顺序也没有问题),甚至不要求索引一定是整数(可以使用任意字符串作为索引)。下图所示的这颗配置树就采用字符串(Foo、Bar和Baz)来作为集合元素的索引。

既然我们能够正确将集合对象通过一个合法的配置树体现出来,那么我们就可以将它转换成配置字典。对于通过上图表示的这个包含三个元素的Profile集合,我们可以采用如下面的表格所示的结构来定义对应的配置字典。

Key

Value

Foo:Gender

Male

Foo:Age

18

Foo:ContactInfo:Email

foo@outlook.com

Foo:ContactInfo:PhoneNo

123

Bar:Gender

Male

Bar:Age

25

Bar:ContactInfo:Email

bar@outlook.com

Bar:ContactInfo:PhoneNo

456

Baz:Gender

Female

Baz:Age

40

Baz:ContactInfo:Email

baz@outlook.com

Baz:ContactInfo:PhoneNo

789

我们依然通过一个简单的实例来演示针对集合的配置绑定。如下面的代码片段所示,我们创建了一个ConfigurationBuilder对象并为之添加了一个MemoryConfigurationProvider,后者按照如表3所示的结构提供了原始的配置数据。我们利用这个ConfigurationBuilder对象创建的Configuration对象并调用这个ConfigurationSection的Get方法将Key为“Profiles”的配置节绑定为一个List<Profile>对象。

在下面演示的代码片段中,我们按照上面表格所示的结构定义了一个Dictionary<string, string>对象,然后以此用创建了一个MemoryConfigurationSource,并将其注册到创建的ConfigurationBuilder对象。我们利用后者生成的配置采用Options模式得到配置绑定生成的Collection<Profile>对象。

   1: Dictionary<string, string> source = new Dictionary<string, string>

   2: {

   3:     ["foo:gender"]                       = "Male",

   4:     ["foo:age"]                          = "18",

   5:     ["foo:contactInfo:emailAddress"]     = "foo@outlook.com",

   6:     ["foo:contactInfo:phoneNo"]          = "123",

   7:  

   8:     ["bar:gender"]                       = "Male",

   9:     ["bar:age"]                          = "25",

  10:     ["bar:contactInfo:emailAddress"]     = "bar@outlook.com",

  11:     ["bar:contactInfo:phoneNo"]          = "456",

  12:  

  13:     ["baz:gender"]                       = "Female",

  14:     ["baz:age"]                          = "36",

  15:     ["baz:contactInfo:emailAddress"]     = "baz@outlook.com",

  16:     ["baz:contactInfo:phoneNo"]          = "789"

  17: };

  18:  

  19: IConfiguration config = new ConfigurationBuilder()

  20:     .Add(new MemoryConfigurationSource { InitialData = source })

  21:     .Build();

  22:  

  23: Collection<Profile> profiles = new ServiceCollection()

  24:     .AddOptions()

  25:     .Configure<Collection<Profile>>(config)

  26:     .BuildServiceProvider()

  27:     .GetService<IOptions<Collection<Profile>>>()

  28:     .Value;

针对集合类型的配置绑定,还有一个不为人知的小细节值得一提。IConfiguration接口的Bind方法在进行集合绑定的时候,如果某个元素绑定失败,并不会有任何的异常会被抛出,该方法会选择下一个元素继续实施绑定。这个特性会造成最终生成的集合对象与原始配置在数量上的不一致。比如我们将上面的程序作了如下的改写,保存原始配置的字典对象包含两个元素,第一个元素的性别从“Male”改为“男”,毫无疑问这个值是不可能转换成Gender枚举对象的,所以针对这个Profile的配置绑定会失败。代码整个程序并不会有任何异常抛出来,但是最终生成的Collection<Profile>将只有一个元素。

   1: Dictionary<string, string> source = new Dictionary<string, string>

   2: {

   3:     ["foo:gender"]                       = "男",

   4:     ["foo:age"]                          = "18",

   5:     ["foo:contactInfo:emailAddress"]     = "foo@outlook.com",

   6:     ["foo:contactInfo:phoneNo"]          = "123",

   7:  

   8:     ["bar:gender"]                       = "Male",

   9:     ["bar:age"]                          = "25",

  10:     ["bar:contactInfo:emailAddress"]     = "bar@outlook.com",

  11:     ["bar:contactInfo:phoneNo"]          = "456"

  12: };

  13:  

  14: IConfiguration config = new ConfigurationBuilder()

  15:     .Add(new MemoryConfigurationSource { InitialData = source })

  16:     .Build();

  17:  

  18: Collection<Profile> profiles = new ServiceCollection()

  19:     .AddOptions()

  20:     .Configure<Collection<Profile>>(config)

  21:     .BuildServiceProvider()

  22:     .GetService<IOptions<Collection<Profile>>>()

  23:     .Value;

  24:  

  25: Debug.Assert(profiles.Count == 1);

我们知道数组是一种特性类型的集合,所以针对数组和集合的配置绑定本质上并没有什么区别。IConfiguration接口的Bind方法本身是可以支持数组绑定的,但是作为IOptions<TOptions>的泛型参数类型TOpions必须是一个具有默认无参构造函数的实例类型,所以Options模式并不支持针对数组的直接绑定,下面这段代码是不能通过编译的。

   1: …

   2: Profile[] profiles = new ServiceCollection()

   3:     .AddOptions()

   4:     .Configure<Profile[]>(config)

   5:     .BuildServiceProvider()

   6:     .GetService<IOptions<Profile[]>>()

   7:     .Value;

虽然我们不能采用Options模式直接将配置绑定为一个数组对象,但我们可以将数组作为某个Options类型的属性成员。如下面的代码片段所示,我们定义了一个Options类型,它具有的唯一属性成员Profiles是一个数组。我们按照复杂对象配置绑定的规则提供原始的配置数据并按照Options模式得到绑定生成的Options对象,最终通过它得到这个Profile数组。

   1: Dictionary<string, string> source = new Dictionary<string, string>

   2: {

   3:     ["profiles:foo:gender"]                       = "Male",

   4:     ["profiles:foo:age"]                          = "18",

   5:     ["profiles:foo:contactInfo:emailAddress"]     = "foo@outlook.com",

   6:     ["profiles:foo:contactInfo:phoneNo"]          = "123",

   7:  

   8:     ["profiles:bar:gender"]                       = "Male",

   9:     ["profiles:bar:age"]                          = "25",

  10:     ["profiles:bar:contactInfo:emailAddress"]     = "bar@outlook.com",

  11:     ["profiles:bar:contactInfo:phoneNo"]          = "456",

  12:  

  13:     ["profiles:baz:gender"]                       = "Female",

  14:     ["profiles:baz:age"]                          = "36",

  15:     ["profiles:baz:contactInfo:emailAddress"]     = "baz@outlook.com",

  16:     ["profiles:baz:contactInfo:phoneNo"]          = "789"

  17: };

  18:  

  19: IConfiguration config = new ConfigurationBuilder()

  20:     .Add(new MemoryConfigurationSource { InitialData = source })

  21:     .Build();

  22:  

  23: Profile[] profiles = new ServiceCollection()

  24:     .AddOptions()

  25:     .Configure<Options>(config)

  26:     .BuildServiceProvider()

  27:     .GetService<IOptions<Options>>()

  28:     .Value

  29:     .Profiles;

  30:  

  31: public class Options

  32: {

  33:    public Profile[] Profiles { get; set; }

  34: }

四、绑定字典

能够通过配置绑定生成的字典是一个实现了IDictionary<string,T>的类型,也就是说配置模型没有对字典的Value未作任何要求,但是字典对象的Key必须是一个字符串。如果采用配置树的形式来表示这么一个字典对象,我们会发现它与针对集合的配置树在结构上是完全一样的。唯一的区别是,集合元素的索引直接变成了字典元素的Key。也就是说上图所示的这棵配置树同样可以表示成一个具有三个元素的Dictionary<string, Profile>对象 ,它们对应的Key分别是“Foo”、“Bar”和“Baz”。

   1: Dictionary<string, string> source = new Dictionary<string, string>

   2: {

   3:     ["foo:gender"]                       = "Male",

   4:     ["foo:age"]                          = "18",

   5:     ["foo:contactInfo:emailAddress"]     = "foo@outlook.com",

   6:     ["foo:contactInfo:phoneNo"]          = "123",

   7:  

   8:     ["bar:gender"]                       = "Male",

   9:     ["bar:age"]                          = "25",

  10:     ["bar:contactInfo:emailAddress"]     = "bar@outlook.com",

  11:     ["bar:contactInfo:phoneNo"]          = "456",

  12:  

  13:     ["baz:gender"]                       = "Female",

  14:     ["baz:age"]                          = "36",

  15:     ["baz:contactInfo:emailAddress"]     = "baz@outlook.com",

  16:     ["baz:contactInfo:phoneNo"]          = "789"

  17: };

  18:  

  19: IConfiguration config = new ConfigurationBuilder()

  20:     .Add(new MemoryConfigurationSource { InitialData = source })

  21:     .Build();

  22:  

  23: Dictionary<string, Profile> profiles = new ServiceCollection()

  24:     .AddOptions()

  25:     .Configure <Dictionary<string, Profile>> (config)

  26:     .BuildServiceProvider()

  27:     .GetService<IOptions <Dictionary<string, Profile >>> ()

  28:     .Value;

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

  1. .NET Core采用的全新配置系统[10]: 配置的同步机制是如何实现的?

    配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.要了解配置同步机制的实现原理,先得从认识一个 ...

  2. .NET Core采用的全新配置系统[2]: 配置模型设计详解

    在<.NET Core采用的全新配置系统[1]: 读取配置数据>中,我们通过实例的方式演示了几种典型的配置读取方式,其主要目的在于使读者朋友们从编程的角度对.NET Core的这个全新的配 ...

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

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

  4. .NET Core采用的全新配置系统[1]: 读取配置数据

    提到“配置”二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义在这两个文 ...

  5. .NET Core采用的全新配置系统[5]: 聊聊默认支持的各种配置源[内存变量,环境变量和命令行参数]

    较之传统通过App.config和Web.config这两个XML文件承载的配置系统,.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种不同配置源的支持.我们可以将内存变量.命令行参 ...

  6. .NET Core采用的全新配置系统[9]: 为什么针对XML的支持不够好?如何改进?

    物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...

  7. .NET Core采用的全新配置系统[7]: 将配置保存在数据库中

    我们在<聊聊默认支持的各种配置源>和<深入了解三种针对文件(JSON.XML与INI)的配置源>对配置模型中默认提供的各种ConfigurationSource进行了深入详尽的 ...

  8. .NET Core采用的全新配置系统[8]: 如何实现配置与源文件的同步

    配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.接下来我们利用一个简单的.NET Core控 ...

  9. .NET Core采用的全新配置系统[6]: 深入了解三种针对文件(JSON、XML与INI)的配置源

    物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...

随机推荐

  1. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  2. 拨开迷雾,找回自我:DDD 应对具体业务场景,Domain Model 到底如何设计?

    写在前面 除了博文内容之外,和 netfocus 兄的讨论,也可以让你学到很多(至少我是这样),不要错过哦. 阅读目录: 迷雾森林 找回自我 开源地址 后记 毫无疑问,领域驱动设计的核心是领域模型,领 ...

  3. Linux CentOS 配置JDK环境

    一.下载JDK 下载JDK的方式有两种: 1.Linux中使用wget下载 1.使用命令安装wget yum install wget 2.下载 wget 'http://download.oracl ...

  4. Redis/HBase/Tair比较

    KV系统对比表 对比维度 Redis Redis Cluster Medis Hbase Tair 访问模式    支持Value大小 理论上不超过1GB(建议不超过1MB) 理论上可配置(默认配置1 ...

  5. RSA算法

    RSA.h #ifndef _RSA_H #define _RSA_H #include<stdio.h> #include<iostream> #include<mat ...

  6. 在 Windows7 上按照 MySQL5.7

    在 Windows7 上按照 MySQL5.7 1.从官网下载最新版本的 MySQL,这里下载的是 mysql-5.7.17-win32: 2.将下载的 mysql-5.7.17-win32.zip ...

  7. Mysql - 查询之关联查询

    查询这块是重中之重, 关系到系统反应时间. 项目做到后期, 都是要做性能测试和性能优化的, 优化的时候, 数据库这块是一个大头. sql格式: select 列名/* from 表名 where 条件 ...

  8. 项目游戏开发日记 No.0x000001

    14软二杨近星(2014551622) 既然已经决定了开发软件, 时不时就要练练手, 还要时不时的去寻找素材, 因为开发的人物设定就是DotA2里面的祈求者, 所以, 就去找了他的相关人物图片和模型, ...

  9. 我的MYSQL学习心得(九) 索引

    我的MYSQL学习心得(九) 索引 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类 ...

  10. Spring6:基于注解的Spring MVC(上篇)

    什么是Spring MVC Spring MVC框架是一个MVC框架,通过实现Model-View-Controller模式来很好地将数据.业务与展现进行分离.从这样一个角度来说,Spring MVC ...