.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 ...
随机推荐
- Asp.Net Mvc 使用WebUploader 多图片上传
来博客园有一个月了,哈哈.在这里学到了很多东西.今天也来试着分享一下学到的东西.希望能和大家做朋友共同进步. 最近由于项目需要上传多张图片,对于我这只菜鸟来说,以前上传图片都是直接拖得控件啊,而且还是 ...
- 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)
一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...
- 使用webstorm+webpack构建简单入门级“HelloWorld”的应用&&引用jquery来实现alert
使用webstorm+webpack构建简单入门级"HelloWorld"的应用&&构建使用jquery来实现 1.首先你自己把webstorm安装完成. 请参考这 ...
- Matlab slice方法和包络法绘制三维立体图
前言:在地球物理勘探,流体空间分布等多种场景中,定位空间点P(x,y,x)的物理属性值Q,并绘制三维空间分布图,对我们洞察空间场景有十分重要的意义. 1. 三维立体图的基本要件: 全空间网格化 网格节 ...
- Hbase的伪分布式安装
Hbase安装模式介绍 单机模式 1> Hbase不使用HDFS,仅使用本地文件系统 2> ZooKeeper与Hbase运行在同一个JVM中 分布式模式– 伪分布式模式1> 所有进 ...
- ls: 无法访问/usr/sbin/smartctl: 没有那个文件或目录
环境:RHEL6.5 + Oracle 11.2.0.4 RAC 在安装RAC时,检查时缺少包 cvuqdisk-1.0.9-1,oracle提供脚本修复安装. 但在执行时报错: [root@orad ...
- DOM的小练习,两个表格之间数据的移动
本次讲的是两个表格之间数据的移动,左边的表格移动到右边,并且左边表格移动内容消失. <head> <meta http-equiv="Content-Type" ...
- Web安全开发之验证码设计不当引发的撞库问题
感谢某电商平台安全工程师feiyu跟我一起讨论这个漏洞的修复.以往在安全测试的过程中后台经常存在验证码不失效果造成的撞库问题,甚至在一些银行或者电商的登录与查存页面同样存在这个问题,一旦造成撞库无论对 ...
- https 安全验证问题
最近为了满足苹果的 https 要求, 经过努力终于写出了方法 验证 SSL 证书是否满足 ATS 要求 nscurl --ats-diagnostics --verbose https://你的域名 ...
- ionic第一坑——ion-slide-box坑(ion-slide分两页的坑)
ionic.views.Slider = ionic.views.View.inherit({ initialize: function (options) { . . . function setu ...