旨在生成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;
文章列表