.NET Core采用的全新配置系统[4]: “Options模式”下各种类型的Options对象是如何绑定的?
旨在生成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对象是如何绑定的?的更多相关文章
- .NET Core采用的全新配置系统[10]: 配置的同步机制是如何实现的?
配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.要了解配置同步机制的实现原理,先得从认识一个 ...
- .NET Core采用的全新配置系统[2]: 配置模型设计详解
在<.NET Core采用的全新配置系统[1]: 读取配置数据>中,我们通过实例的方式演示了几种典型的配置读取方式,其主要目的在于使读者朋友们从编程的角度对.NET Core的这个全新的配 ...
- .NET Core采用的全新配置系统[3]: “Options模式”下的配置是如何绑定为Options对象
配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置.值得推荐的做法就是采用<.NET Core采用的全新配置系统[1]: 读取 ...
- .NET Core采用的全新配置系统[1]: 读取配置数据
提到“配置”二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义在这两个文 ...
- .NET Core采用的全新配置系统[5]: 聊聊默认支持的各种配置源[内存变量,环境变量和命令行参数]
较之传统通过App.config和Web.config这两个XML文件承载的配置系统,.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种不同配置源的支持.我们可以将内存变量.命令行参 ...
- .NET Core采用的全新配置系统[9]: 为什么针对XML的支持不够好?如何改进?
物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...
- .NET Core采用的全新配置系统[7]: 将配置保存在数据库中
我们在<聊聊默认支持的各种配置源>和<深入了解三种针对文件(JSON.XML与INI)的配置源>对配置模型中默认提供的各种ConfigurationSource进行了深入详尽的 ...
- .NET Core采用的全新配置系统[8]: 如何实现配置与源文件的同步
配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.接下来我们利用一个简单的.NET Core控 ...
- .NET Core采用的全新配置系统[6]: 深入了解三种针对文件(JSON、XML与INI)的配置源
物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...
随机推荐
- 多线程爬坑之路-Thread和Runable源码解析
多线程:(百度百科借一波定义) 多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提 ...
- Python多线程爬虫爬取电影天堂资源
最近花些时间学习了一下Python,并写了一个多线程的爬虫程序来获取电影天堂上资源的迅雷下载地址,代码已经上传到GitHub上了,需要的同学可以自行下载.刚开始学习python希望可以获得宝贵的意见. ...
- from表单提交数据之后,后台对象接受不到值
如果SSH框架下,前段页面通过from表单提交数据之后,在后台对象显示空值,也就是接收不到值得情况下.首先保证前段输入框有值,这个可以在提交的时候用jQuery的id或者name选择器alert弹出测 ...
- Sass之坑Compass编译报错
前段时间在使用Compass时遇到了其为难处理的一个坑,现记录到博客希望能帮助到各位. 一.问题: 利用Koala或者是gulp编译提示如下,截图为koala编译提示错误: 二.解决办法 从问题截图上 ...
- Android 旋转屏幕--处理Activity与AsyncTask的最佳解决方案
一.概述 运行时变更就是设备在运行时发生变化(例如屏幕旋转.键盘可用性及语言).发生这些变化,Android会重启Activity,这时就需要保存activity的状态及与activity相关的任务, ...
- iOS--->微信支付小结
iOS--->微信支付小结 说起支付,除了支付宝支付之外,微信支付也是我们三方支付中最重要的方式之一,承接上面总结的支付宝,接下来把微信支付也总结了一下 ***那么首先还是由公司去创建并申请使用 ...
- VMware下对虚拟机Ubuntu14系统所在分区sda1进行磁盘扩容
VMware下对虚拟机Ubuntu14系统所在分区sda1进行磁盘扩容 一般来说,在对虚拟机里的Ubuntu下的磁盘进行扩容时,都是添加新的分区,而并不是对其系统所在分区进行扩容,如在此链接中http ...
- Harmonic Number(调和级数+欧拉常数)
题意:求f(n)=1/1+1/2+1/3+1/4-1/n (1 ≤ n ≤ 108).,精确到10-8 (原题在文末) 知识点: 调和级数(即f(n))至今没有一个完全正确的公式, ...
- AutoMapper(七)
返回总目录 Null值替换 如果源类型的成员链上的属性值为Null,Null值替换允许提供一个可替换的值.下面有两个类Person和PersonInfo类,都有一个属性Title(头衔),从Perso ...
- 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gulp专家
系列目录 前端构建大法 Gulp 系列 (一):为什么需要前端构建 前端构建大法 Gulp 系列 (二):为什么选择gulp 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gul ...