.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();

我们知道数组是一种特性类型的集合,所以针对数组和集合的配置绑定本质上并没有什么区别。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;

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-09-20 18:52:20

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

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

在<.NET Core采用的全新配置系统[1]: 读取配置数据>中,我们通过实例的方式演示了几种典型的配置读取方式,其主要目的在于使读者朋友们从编程的角度对.NET Core的这个全新的配置系统具有一个大体上的认识,接下来我们从设计的维度来重写认识它.通过上面演示的实例我们知道,配置的编程模型涉及到三个核心对象,它们分别是Configuration.ConfigurationSource和ConfigurationBuilder.如果从设计层面来审视这个配置系统,还缺少另一个名为Configu

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

配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.要了解配置同步机制的实现原理,先得从认识一个名为ConfigurationReloadToken的类型开始. [ 本文已经同步到<ASP.NET Core框架揭秘>之中] 目录 一.从ConfigurationReloadToken说起 二.Configuration对象与配置文件的同步 三.应用重新加载的配置 四.同步流程总结 一.从Co

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

配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置.值得推荐的做法就是采用<.NET Core采用的全新配置系统[1]: 读取配置数据>最后演示的方式将相关的配置定义成一个Options类型,并采用与类型定义想匹配的结构来定义原始的配置,这样就能利用它们之间的映射关系将读取的配置数据绑定为Options对象,我们将这种编程模式称为"Options模式". [ 本文已经同步到<ASP.NET Core框

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

提到"配置"二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义在这两个文件之中.到了.NET Core的时代,很多我们习以为常的东西都发生了改变,其中也包括定义配置的方式.总的来说,新的配置系统显得更加轻量级,并且具有更好的扩展性,其最大的特点就是支持多样化的数据源.我们可以采用内存的变量作为配置的数据源,也可以直接配置定义在持久化的文件甚至数据库中.由于很多

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

较之传统通过App.config和Web.config这两个XML文件承载的配置系统,.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种不同配置源的支持.我们可以将内存变量.命令行参数.环境变量和物理文件作为原始配置数据的来源,如果采用物理文件作为配置源,我们可以选择不同的格式(比如XML.JSON和INI等) .如果这些默认支持的配置源形式还不能满足你的需求,我们还可以通过注册自定义ConfigurationSource的方式将其他形式数据作为我们的配置来源. [ 本文已经同

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

我们在<聊聊默认支持的各种配置源>和<深入了解三种针对文件(JSON.XML与INI)的配置源>对配置模型中默认提供的各种ConfigurationSource进行了深入详尽的介绍,如果它们依然不能满足项目中的配置需求,我们可以还可以通过自定义ConfigurationProvider来支持我们希望的配置来源.就配置数据的持久化方式来说,将培植存储在数据库中应该是一种非常常见的方式,接下来我们就是创建一个针对数据库的ConfigurationSource,它采用最新的Entity

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

配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.接下来我们利用一个简单的.NET Core控制台应用来演示针对文件的配置会涉及到数据同步的问题,我们希望应用能够对原始配置文件实施监控,并在文件内容发生改变的时候从新加载并应用新的配置.针对JSON文件的配置源通过JsonConfigurationSource类型来表示,该类型定义在"Microsoft.Extensions.Configur

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

物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigurationSource和IniConfigurationSource. [ 本文已经同步到<ASP.NET Core框架揭秘>之中] 目录 一.FileConfigurationSource  & FileConfigurationProvider 二.JsonConfigurationSo

android系统在静音模式下关闭camera拍照声音的方法

话说为了防止偷拍,业内有不成文规定,手机公司在做camera时,点击拍照和录像键的时候,必须要有提示音.因此,google也就非常人性化的将播放 拍照声音的函数,放到了cameraService中,防止开发者能开发出不响的camera,从而只要调用拍照函数,一定会响,这是写死在 framework中的. 话说这个规定在当今有点不合时宜,这不,今天我收到测试提的一个BUG,说是公司的新需求,要求在静音模式下拍照声音也得取消.这么无耻的需求,也许就在我们中国最大的山寨手机公司才会提到.废话不多说,看