The new configuration system uses .NET Core [4]: "Options Mode" for various types of objects under Options is how binding?

Configuration Options object intended to generate bindings implemented on extension methods Bind IConfiguration interface. Configure the binding target may be a simple type of primitive types , or may be a custom data type , it may also be an array , set or dictionary type. By the previous description we know ConfigurationProvider after the original configuration data read out will convert it to the data dictionary Key and Value are strings, then for these disparate target types, how original configuration data through the data dictionary form to reflect it? [This article has been synchronized to the " ASP.NET Framework Core Secret " among]

Contents
I. Binding simple data types
II binding complex data type
III binding collection target
four bindings dictionary

A binding simple data types

Let's say for binding configuration for simple data types. Here the so-called simple data types and complex data types defined in only one standard, that is, whether to support the type of data conversion from string. In other words, simply type object can be directly converted from a string through the complex types of objects can not. If the target type is a simple type, the configuration is performed only when bound value (Value property embodied ConfigurationSection) To configure the item into the corresponding data types it.

For simple binding type of configuration, in addition to calling the above extension methods Bind be done outside, we actually have a better choice, that is to call another extension IConfiguration interface method GetValue . GetValue method always value (string) a CI atom into a target type, so we call this method is in addition to targeting type, but also need to specify the key parameter configuration items by atom relative to the current object Configuration path, that is to say not only can be specified as the key parameter Key (such as "Foo") child configuration item may be set relative to the current node for each of the following configuration sections (such as "Foo: Bar: Baz") . If the specified configuration section has no value, or the configuration section does not exist, the method returns defaultValue parameter specified by default.

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

In addition to the above-described GetValue method, IConfiguration interface also has three GetValue method overload, which ultimately will call this method to accomplish the above configuration binding for simple types. The first two types of method of targeting binding form of generic parameters, if not explicitly specified default value, mean default value is 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);

In the following program, we demonstrate our binding function for the three types of configuration data. The first two types are Double and enumeration, which is inherently supports simple type derived from the string. The third type is a Point that represents our self-defined two-dimensional coordinates of points, because we TypeConverterAttribute characteristics through the application of registered TypeConverter (PointTypeConverter) a string conversion support for it, it is also shown in a simple type.

   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: }

Second, the binding complex data types

Here the so-called complex type represents a type of attribute data member has. If a complex object represented by a tree, the leaf nodes carry all the data, and the data types are simple types of leaf nodes. If a complex object to provide all of the raw data by a data dictionary, then the dictionary need only contain a value corresponding to the leaf node. As to how to reflect the complex objects through a dictionary object structure, the path we just need to leaf node where it can be used as Key elements of the dictionary.

   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: }

As shown in the above code fragment, we define a basic personal information Profile class indicated, the definition of which three attributes (Gender, Age and the ContactInfo) represent gender, age, and contact information. Indicate contact information ContactInfo object has two properties (EmailAddress and PhoneNo) represent the e-mail address and telephone number. Profile of a complete object can be embodied by the tree shown below.

8

If you need to represent a complete configuration of the Profile object through a form, we only need four leaf nodes (gender, age, email address and telephone number) corresponding to data defined in the configuration. Dictionary data for bearer configuration data, we need the following manner shown in these four table leaf node as a path dictionary Key elements.

 

Key

Value

Gender

Male

Age

18

ContactInfo:Email

[email protected]

ContactInfo:PhoneNo

123456789

 

As shown in the above code fragment, we create an object and to whom ConfigurationBuilder added a MemoryConfigurationProvider, which provides a data structure according to the original configuration as shown in Table 2. We are fully in accordance with the Options program mode these original configuration property is bound to a Profile object.

   1: Dictionary<string, string> source = new Dictionary<string, string>
   2: {
   3:     ["gender"]                       = "Male",
   4:     ["age"]                          = "18",
   5:     ["contactInfo:emailAddress"]     = "[email protected]",
   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;


Third, the binding collection target

Here refers to a collection type implements ICollection <T> all types of interfaces. If a set is represented by a tree, it may be a collection of elements as a set of child nodes of the object itself. Options such as a type of object is a set of Profile, which corresponds to the configuration tree has a structure shown in FIG.

9

The tree diagram shown above configuration tree, we use a zero-based index (continuously incremented integer beginning with zero) to represent the position of each object in the collection of Profile. In fact the configuration tree for a set of objects is not particularly required, it does not require an index must be an integer from scratch ( "1,2,3" can be obtained also in this order), they must not require continuity ( "2,4" in this order is not a problem), it does not even require the index must be an integer (any string may be used as an index). Shown below the stone on the use of a string configuration tree (Foo, Bar and Baz) as an index to a collection of elements.

10 

Since we can properly reflected in the collection of objects through a legitimate configuration tree, then we can convert it into a configured dictionary. Profile for this set contains three elements indicated by the figure, we can structure as shown in the table below to define a corresponding configuration of the dictionary used.

 

Key

Value

Foo:Gender

Male

Foo:Age

18

Foo:ContactInfo:Email

[email protected]

Foo:ContactInfo:PhoneNo

123

Bar:Gender

Male

Bar:Age

25

Bar:ContactInfo:Email

[email protected]

Bar:ContactInfo:PhoneNo

456

Baz:Gender

Female

Baz:Age

40

Baz:ContactInfo:Email

[email protected]

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"]     = "[email protected]",
   6:     ["foo:contactInfo:phoneNo"]          = "123",
   7:  
   8:     ["bar:gender"]                       = "Male",
   9:     ["bar:age"]                          = "25",
  10:     ["bar:contactInfo:emailAddress"]     = "[email protected]",
  11:     ["bar:contactInfo:phoneNo"]          = "456",
  12:  
  13:     ["baz:gender"]                       = "Female",
  14:     ["baz:age"]                          = "36",
  15:     ["baz:contactInfo:emailAddress"]     = "[email protected]",
  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"]     = "[email protected]",
   6:     ["foo:contactInfo:phoneNo"]          = "123",
   7:  
   8:     ["bar:gender"]                       = "Male",
   9:     ["bar:age"]                          = "25",
  10:     ["bar:contactInfo:emailAddress"]     = "[email protected]",
  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"]     = "[email protected]",
   6:     ["profiles:foo:contactInfo:phoneNo"]          = "123",
   7:  
   8:     ["profiles:bar:gender"]                       = "Male",
   9:     ["profiles:bar:age"]                          = "25",
  10:     ["profiles:bar:contactInfo:emailAddress"]     = "[email protected]",
  11:     ["profiles:bar:contactInfo:phoneNo"]          = "456",
  12:  
  13:     ["profiles:baz:gender"]                       = "Female",
  14:     ["profiles:baz:age"]                          = "36",
  15:     ["profiles:baz:contactInfo:emailAddress"]     = "[email protected]",
  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"]     = "[email protected]",
   6:     ["foo:contactInfo:phoneNo"]          = "123",
   7:  
   8:     ["bar:gender"]                       = "Male",
   9:     ["bar:age"]                          = "25",
  10:     ["bar:contactInfo:emailAddress"]     = "[email protected]",
  11:     ["bar:contactInfo:phoneNo"]          = "456",
  12:  
  13:     ["baz:gender"]                       = "Female",
  14:     ["baz:age"]                          = "36",
  15:     ["baz:contactInfo:emailAddress"]     = "[email protected]",
  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;

 

旨在生成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对象可以通过如下图所示的树来体现。

8

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

 

Key

Value

Gender

Male

Age

18

ContactInfo:Email

[email protected]

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"]     = "[email protected]",
   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的集合,它对应的配置树具有如下图所示的结构。

9

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

10 

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

 

Key

Value

Foo:Gender

Male

Foo:Age

18

Foo:ContactInfo:Email

[email protected]

Foo:ContactInfo:PhoneNo

123

Bar:Gender

Male

Bar:Age

25

Bar:ContactInfo:Email

[email protected]

Bar:ContactInfo:PhoneNo

456

Baz:Gender

Female

Baz:Age

40

Baz:ContactInfo:Email

[email protected]

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"]     = "[email protected]",
   6:     ["foo:contactInfo:phoneNo"]          = "123",
   7:  
   8:     ["bar:gender"]                       = "Male",
   9:     ["bar:age"]                          = "25",
  10:     ["bar:contactInfo:emailAddress"]     = "[email protected]",
  11:     ["bar:contactInfo:phoneNo"]          = "456",
  12:  
  13:     ["baz:gender"]                       = "Female",
  14:     ["baz:age"]                          = "36",
  15:     ["baz:contactInfo:emailAddress"]     = "[email protected]",
  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"]     = "[email protected]",
   6:     ["foo:contactInfo:phoneNo"]          = "123",
   7:  
   8:     ["bar:gender"]                       = "Male",
   9:     ["bar:age"]                          = "25",
  10:     ["bar:contactInfo:emailAddress"]     = "[email protected]",
  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"]     = "[email protected]",
   6:     ["profiles:foo:contactInfo:phoneNo"]          = "123",
   7:  
   8:     ["profiles:bar:gender"]                       = "Male",
   9:     ["profiles:bar:age"]                          = "25",
  10:     ["profiles:bar:contactInfo:emailAddress"]     = "[email protected]",
  11:     ["profiles:bar:contactInfo:phoneNo"]          = "456",
  12:  
  13:     ["profiles:baz:gender"]                       = "Female",
  14:     ["profiles:baz:age"]                          = "36",
  15:     ["profiles:baz:contactInfo:emailAddress"]     = "[email protected]",
  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"]     = "[email protected]",
   6:     ["foo:contactInfo:phoneNo"]          = "123",
   7:  
   8:     ["bar:gender"]                       = "Male",
   9:     ["bar:age"]                          = "25",
  10:     ["bar:contactInfo:emailAddress"]     = "[email protected]",
  11:     ["bar:contactInfo:phoneNo"]          = "456",
  12:  
  13:     ["baz:gender"]                       = "Female",
  14:     ["baz:age"]                          = "36",
  15:     ["baz:contactInfo:emailAddress"]     = "[email protected]",
  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;

 

Guess you like

Origin www.cnblogs.com/webenh/p/11589896.html