配置的原子結構就是單純的鍵值對,并且鍵和值都是字符串,但是在真正的項目開發中我們一般不會單純地以鍵值對的形式來使用配置。值得推薦的做法就是采用《.NET Core采用的全新配置系統[1]: 讀取配置數據》最后演示的方式將相關的配置定義成一個Options類型,并采用與類型定義想匹配的結構來定義原始的配置,這樣就能利用它們之間的映射關系將讀取的配置數據綁定為Options對象,我們將這種編程模式稱為“Options模式”。 [ 本文已經同步到《ASP.NET Core框架揭秘》之中]
目錄
一、配置綁定
二、擴展方法AddOptions
三、擴展方法Configure
四、Options對象的創建
一、配置綁定
對于一個Options對象來說,如果我們將其數據成員(這里主要指屬性成員)視為其子節點,那么一個Options對象同樣具有樹形層次化結構,這與通過Configuration對象表示的配置樹在結構上并沒有本質的區別。如果Options類型的數據成員定義與配置樹結構具有匹配的結構,那么將后者綁定為一個對應類型的Options對象是一件很容易的事情,對于這種將一個Configuration對象綁定為對應Options對象的行為簡稱為“配置綁定”。
配置綁定讓我們可以根據得到的Configuration對象生成相應的Options對象,相關的API定義在“Microsoft.Extensions.Configuration.Binder”這個NuGet包中,后者為IConfiguration接口定義了如下一個GetValue方法得到綁定生成的Options對象。在調用這個放過的時候,我們會創建一個空的Options對象并將其作為參數,該方法會將Configuration承載的配置數據綁定到Options對象上。
1: public static class ConfigurationBinder
2: {
3: public static void Bind(this IConfiguration configuration, object instance);
4: }
配置綁定的目標類型可以是一個簡單的基元類型,也可以是一個自定義數據類型,還可以是一個數組、集合或者字典類型。上述的這個Bind方法在進行配置綁定的過程,針對不同的目標類型,它會采用不同的策略。至于該方法具體的實現原理,我們會在后續的部分予以單獨介紹,而目前介紹的重點是Options模式采用的API在背后是如何調用這個方法得到所需的Options對象的。
我們在回顧一下《.NET Core采用的全新配置系統[1]: 讀取配置數據》演示的采用Options模式讀取配置的例子。Options模式是對依賴注入的應用,我們知道針對依賴注入的編程只涉及兩個方面,即注冊相應的服務到ServiceCollection對象上,在利用后者創建相應的ServiceProvider來提供我們所需的服務對象。如下面的代碼片段所示,Options模式最終的目的是利用ServiceProvider得到一個類型為IOptions<TOptions>的服務對象,后者的Value通過配置綁定生成的Options對象。為了能夠得到所需的服務對象,它借助兩個擴展方法AddOptions和Configure<TOptions>注冊了必要的服務。
1: IConfiguration config = ...;
2: FormatOptions options = new ServiceCollection()
3: .AddOptions()
4: .Configure<FormatOptions>(config.GetSection("Format"))
5: .BuildServiceProvider()
6: .GetService<IOptions<FormatOptions>>()
7: .Value;
二、擴展方法AddOptions
依然Options對象最終是利用依賴注入的方式創建的一個類型為IOptions<TOptions>的服務對象得到的,我們就先來認識一下這個接口。這是一個泛型接口,泛型參數類型TOptions代碼的正式Options對象對應的類型。IOptions<TOptions>接口的定義如下,它只有一個唯一的只讀屬性Value返回我們所需的Options對象。
1: public interface IOptions<out TOptions> where TOptions: class, new()
2: {
3: TOptions Value { get; }
4: }
當我們調用ServiceCollection的AddOptions的時候,該方法僅僅是按照如下的方式針對該類型注冊了一個服務而已,這個服務的真實類型為OptionsManager <TOptions> ,注冊的服務采用的生命周期模式為Singleton。換句話說,配置綁定生成的Options對象最終返回的實際上是通過OptionsManager <TOptions> 創建的。
1: public static IServiceCollection AddOptions(this IServiceCollection services)
2: {
3: services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
4: return services;
5: }
如下所示的是 OptionsManager <TOptions> 類型的定義,我們可以看到它的構造函數接受一個元素類型為IConfigureOptions<TOptions>的集合作為參數,我們將實現了該接口的類型以及對應對象統稱為ConfigureOptions<TOptions>。IConfigureOptions<TOptions>接口定義了一個唯一的Configure方法,該方法將一個Options對象作為輸入參數。從定義可以看出一個ConfigureOptions<TOptions>對象的作用與一個類型為Action<TOptions>的委托對象,所以對于它的實現類型ConfigureOptions<TOptions>來說,對應的對象就直接通過一個Action<TOptions>對象來創建。
1: public class OptionsManager<TOptions> : IOptions<TOptions> where TOptions: class, new()
2: {
3: public OptionsManager(IEnumerable<IConfigureOptions<TOptions>> setups);
4: public virtual TOptions Value { get; }
5: }
6:
7: public interface IConfigureOptions<in TOptions> where TOptions: class
8: {
9: void Configure(TOptions options);
10: }
11:
12: public class ConfigureOptions<TOptions>: IConfigureOptions<TOptions> where TOptions : class, new()
13: {
14: public Action<TOptions> Action { get; private set; }
15: public ConfigureOptions(Action<TOptions> action)
16: {
17: this.Action = action;
18: }
19: public void Configure(TOptions options)
20: {
21: this.Action(options);
22: }
23: }
Options對象的創建體現在 OptionsManager <TOptions>類型的Value屬性上。該屬性的實現非常簡單,它先調用默認無參構造函數(Options類型必須具有一個默認無參構造函數)創建一個空的Options對象,在返回之前,它會將其遞交給初始化時指定的ConfigureOptions<TOptions>對象進行逐個處理。毫無疑問,針對Bind方法的調用肯定是通過某個ConfigureOptions<TOptions>對象參與到整個流程之中的,具體的實現自然與另一個擴展方法Configure有關。
三、擴展方法Configure
Options模式僅僅涉及到針對ServiceCollection的兩個擴展方法(AddOptions和Configure<TOptions>),前者將服務IOptions<TOptions>/ OptionsManager <TOptions>注冊到ServiceCollection之上,后者又作了怎樣的服務注冊呢?
1: public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config) where TOptions: class
2: {
3: return services.AddSingleton<IConfigureOptions<TOptions>>( new ConfigureFromConfigurationOptions<TOptions>(config));
4: }
5:
6: public class ConfigureFromConfigurationOptions<TOptions> :ConfigureOptions<TOptions> where TOptions : class
7: {
8: public ConfigureFromConfigurationOptions(IConfiguration config)
9: : base(options => config.Bind(options))
10: { }
11: }
從上面的代碼片段可以看出,當我們調用ServiceCollection的擴展方法Configure<TOptions>時,該方法會利用指定 的Configuration對象創建一個ConfigureFromConfigurationOptions對象,并以服務類型IConfigureOptions<TOptions>注冊到ServiceCollection上,采用的生命周期模式為Singleton。至于類型ConfigureFromConfigurationOptions,它是上面介紹的ConfigureOptions<TOptions>類型的繼承者,創建該對象指定的Action<TOptions>委托對象通過調用Configuration對象的擴展方法Bind最終實現了配置綁定。
四、Options對象的創建
Options編程模式的背后以兩個注冊到ServiceCollection的服務為核心,這兩個服務對應的服務接口分別是IOptions<TOptions>和IConfigureOptions<TOptions>,前者直接提供最終綁定了配置數據的Options對象,后者則在Options對象返回之前對它實施相應的初始化工作。這個兩個服務分別通過擴展方法AddOptions和Configure方法注冊到指定的ServiceCollection之中,服務的真實類型分別是OptionsManager<TOptions>和ConfigureFromConfigurationOptions<TOptions>,后者派生于ConfigureOptions<TOptions>。下圖所示的UML體現了Options模型中涉及的這些接口/類型以及它們之間的關系。
文章列表