提到“配置”二字,我想絕大部分.NET開發人員腦海中會立馬浮現出兩個特殊檔案的身影,那就是我們再熟悉不過的app.config和web.config,多年以來我們已經習慣了將結構化的配置資訊定義在這兩個檔案之中。到了.NET Core的時候,很多我們習以為常的東西都發生了改變,其中也包括定義配置的方式。總的來說,新的配置系統顯得更加輕量級,並且具有更好的擴充性,其最大的特點就是支援多樣化的資料來源。我們可以採用記憶體的變數作為配置的資料來源,也可以直接配置定義在持久化的檔案甚至資料庫中。
由於很多人都不曾接觸過這個採用全新設計的配置系統,為了讓大家對此有一個感官的認識,我們先從編程的角度對它作一個初體驗。針對配置的API涉及三個對象,它們分別是Configuration、ConfigurationBuilder和ConfigurationProvider,配置模型中具有相應的介面來表示它們。這三個對象之間的關係很清晰,Configuration對象承載著在編程過程中使用的配置資訊,ConfigurationProvider則是配置資訊未經處理資料源的提供者,兩者之間溝通由ConfigurationBuilder來完成,它利用ConfigurationProvider提取來源資料將其轉換為Configuration對象。
一、以鍵-值對的形式讀取配置
雖然在大部分情況下的配置資訊從整體來說都具有一個結構化的層次關係,但是“原子”配置項都以最簡單的“鍵-值對”的形式來體現,並且鍵和值都是字串,接下來我們會通過一個簡單的執行個體來示範如何以索引值對的形式來讀取配置。我們建立一個針對ASP.NET Core的控制台應用,並在project.json中按照如下的方式添加針對“Microsoft.Extensions.Configuration”這個NuGet包的依賴,配置模型就實現在這個包中。
{ ... "dependencies": { "Microsoft.Extensions.Configuration": "1.0.0-rc1-final" }, }
假設我們的應用需要通過配置來設定日期/時間的顯示格式,為此我們定義了如下一個DateTimeFormatSettings類,它的四個屬性體現了DateTime對象的四種顯示格式(分別為長日期/時間和短日期/時間)。
public class DateTimeFormatSettings { public string LongDatePattern { get; set; } public string LongTimePattern { get; set; } public string ShortDatePattern { get; set; } public string ShortTimePattern { get; set; } //其他成員 }
我們希望通過配置的形式來控制由DateTimeFormatSettings的四個屬性體現的日期/時間顯示格式,所以我們為它定義了一個建構函式。如下面的程式碼片段所示,該建構函式具有一個IConfiguration介面類型的參數,它正式承載相關配置資訊的Configuration對象。我們調用Configuration對象的索引並指定相應配置項的Key來得到其Value。
public class DateTimeFormatSettings { //其他成員 public DateTimeFormatSettings (IConfiguration configuration) { this.LongDatePattern = configuration["LongDatePattern"]; this.LongTimePattern = configuration["LongTimePattern"]; this.ShortDatePattern = configuration["ShortDatePattern"]; this.ShortTimePattern = configuration["ShortTimePattern"]; } }
要建立一個體現當前配置的DateTimeFormatSettings對象,我們必須向得到這個承載相關配置資訊的Configuration對象。正如我們上面所說,Configuration對象是由ConfigurationBuilder建立的,而原始的配置資訊則是通過相應的ConfigurationProvider來讀取的,所以建立一個Configuration對象的正確編程方式是先建立一個ConfigurationBuilder對象,然後為之添加一個或者多個ConfigurationProvider對象,最後利用ConfigurationBuilder來建立我們需要的Configuration對象。
按照上述的編程模式,我們在一個控制台應用中編寫了如下的程式。我們建立了一個類型為ConfigurationBuilder的對象,調用其Add方法添加的ConfigurationProvider是一個類型為MemoryConfigurationProvider的對象。顧名思義,MemoryConfigurationProvider利用記憶體中的對象來提供原始的配置資訊,具體來說這些原始的配置資訊儲存在一個元素類型為KeyValuePair<string, string>的集合之中。我們最終調用ConfigurationBuilder的Build方法擷取用於建立DateTimeFormatSettings對象所需的Configuration。
public class Program { public static void Main(string[] args) { Dictionary<string, string> source = new Dictionary<string, string> { ["LongDatePattern"] = "dddd, MMMM d, yyyy", ["LongTimePattern"] = "h:mm:ss tt", ["ShortDatePattern"] = "M/d/yyyy", ["ShortTimePattern"] = "h:mm tt" }; IConfiguration configuration = new ConfigurationBuilder() .Add(new MemoryConfigurationProvider(source)) .Build(); DateTimeFormatSettings settings = new DateTimeFormatSettings(configuration); Console.WriteLine("{0,-16}: {1}", "LongDatePattern", settings.LongDatePattern); Console.WriteLine("{0,-16}: {1}", "LongTimePattern", settings.LongTimePattern); Console.WriteLine("{0,-16}: {1}", "ShortDatePattern", settings.ShortDatePattern); Console.WriteLine("{0,-16}: {1}", "ShortTimePattern", settings.ShortTimePattern); } }
為了驗證根據配置建立的DateTimeFormatSettings對象與配置未經處理資料之間的關係,我們將它的四個屬性輸出於控制台上。當這個程式執行之後將在控制台上產生如下所示的輸出,可以看出它正是我們提供的配置的真實反映。
LongDatePattern : dddd, MMMM d, yyyy
LongTimePattern : h:mm:ss tt
ShortDatePattern: M/d/yyyy
ShortTimePattern: h:mm tt
二、 讀取結構化的配置
真實項目中涉及的配置大都具有一個結構化的階層,所以在配置模型中的Configuration對象同樣具有這樣的結構。結構化的配置具有一個樹形階層,而一個Configuration對象表示的是組成這棵配置樹的某個節點,這棵配置樹則可以通過作為根節點的Configuration對象來體現。體現為索引值對的原子配置項一般至存在於作為葉子節點的Configuration對象中,非葉子節點的Configuration包含一組子節點,而每個子節點同樣是一個Configuration對象。
接下來我們同樣以執行個體的方式來示範如何定義並讀取具有層次化結構的配置。我們依然沿用上一節的應用情境,現在我們不僅僅需要設定日期/時間的格式,還需要設定其他資料類型的格式,比如表示貨幣的Decimal類型。為此我們定義了如下一個CurrencyDecimalFormatSettings類,它的屬性Digits和Symbol分別表示小數位元和貨幣符號,一個CurrencyDecimalFormatSettings對象依然是利用一個表示配置的Configuration對象來建立的。
{ public int Digits { get; set; } public string Symbol { get; set; } public CurrencyDecimalFormatSettings(IConfiguration configuration) { this.Digits = int.Parse(configuration["Digits"]); this.Symbol = configuration["Symbol"]; } }
我們定義了另一個名為FormatSettings的類型來表示針對不同資料類型的格式設定。如下面的程式碼片段所示,它的兩個屬性DateTime和CurrencyDecimal分別表示針對日期/時間和貨幣數位格式設定。FormatSettings依然具有一個參數類型為IConfiguration介面的建構函式,它的兩個屬性均在此建構函式中被初始化。值得注意的是初始化這兩個屬性採用的是當前Configuration的“子配置節”,通過指定配置節名稱調用GetSection方法獲得。
public class FormatSettings{ public DateTimeFormatSettings DateTime { get; set; } public CurrencyDecimalFormatSettings CurrencyDecimal { get; set; } public FormatSettings(IConfiguration configuration) { this.DateTime = new DateTimeFormatSettings(configuration.GetSection("DateTime")); this.CurrencyDecimal = new CurrencyDecimalFormatSettings(configuration.GetSection("CurrencyDecimal")); }}
在我們上面示範的執行個體中,我們通過以一個MemoryConfigurationProvider對象來提供原始的配置資訊。由於承載原始配置資訊的是一個元素類型為KeyValuePair<string, string>的集合,所以原始配置在實體儲存體上並不具有樹形化的階層,那麼它如何能夠最終提供一個結構化的Configuration對象呢?其實很簡單,雖然MemoryConfigurationProvider對象只能將配置資訊儲存為簡單的“資料字典”,但是如果將Configuration對象在配置樹中體現的路徑作為Key,這個資料字典在邏輯上實際上就具有了一棵樹的結構。實際上MemoryConfigurationProvider就是這麼做的,這體現在我們如下所示的程式之中。
class Program { static void Main(string[] args) { Dictionary<string, string> source = new Dictionary<string, string> { ["Format:DateTime:LongDatePattern"] = "dddd, MMMM d, yyyy", ["Format:DateTime:LongTimePattern"] = "h:mm:ss tt", ["Format:DateTime:ShortDatePattern"] = "M/d/yyyy", ["Format:DateTime:ShortTimePattern"] = "h:mm tt", ["Format:CurrencyDecimal:Digits"] = "2", ["Format:CurrencyDecimal:Symbol"] = "$", }; IConfiguration configuration = new ConfigurationBuilder() .Add(new MemoryConfigurationProvider(source)) .Build(); FormatSettings settings = new FormatSettings(configuration.GetSection("Format")); Console.WriteLine("DateTime:"); Console.WriteLine("\t{0,-16}: {1}", "LongDatePattern", settings.DateTime.LongDatePattern); Console.WriteLine("\t{0,-16}: {1}", "LongTimePattern", settings.DateTime.LongTimePattern); Console.WriteLine("\t{0,-16}: {1}", "ShortDatePattern", settings.DateTime.ShortDatePattern); Console.WriteLine("\t{0,-16}: {1}\n", "ShortTimePattern", settings.DateTime.ShortTimePattern); Console.WriteLine("CurrencyDecimal:"); Console.WriteLine("\t{0,-16}: {1}", "Digits", settings.CurrencyDecimal.Digits); Console.WriteLine("\t{0,-16}: {1}", "Symbol", settings.CurrencyDecimal.Symbol); }}
如上面的程式碼片段所示,建立MemoryConfigurationProvider對象採用的字典對象包含6個基本的配置項,為了讓它們在邏輯上具有一個樹形化階層,所以的Key實際上體現了每個配置項所在配置節在配置樹中的路徑,路徑採用冒號(“:”)進行分割。改程式執行之後會在控制台上呈現如下所示的輸出結果。
DateTime: LongDatePattern : dddd, MMMM d, yyyy LongTimePattern : h:mm:ss tt ShortDatePattern: M/d/yyyy ShortTimePattern: h:mm tt CurrencyDecimal: Digits : 2 Symbol : $
三、將結構化配置直接綁定為對象
在真正的項目開發過程中,我們都不會直接使用直接讀取的配置,而都傾向於像我們示範的兩個執行個體一樣通過建立相應的類型(比如DateTimeFormatSettings、CurrencyDecimalSettings和FormatSettings)來定義一組相關的配置選項(Option),我們將定義配置選項(Option)的這些類型稱為Option類型。在上面示範的執行個體中,為了建立這些封裝配置的對象,我們都是採用手工讀取配置的形式,如果定義的配置項太多的話,逐條讀取配置項其實是一項非常繁瑣的工作。
對於一個對象來說,如果我們將它的屬性視為它的子節點,一個對象同樣具有類似於Configuration對象的樹形層次化結構。如果我們根據某個Option類型的結構來定義配置,或者反過來根據配置的結構來定義這個Option類型,那麼Option類型的屬性成員將與某個配置節具有一一對應的關係,那麼原則上我們可以自動將配置資訊綁定為一個具體的Option對象。
ASP.NET Core針對配置的Option模型(OptionModel)協助我們實現了從配置到Option對象之間的綁定,接下來我們就對此做一個簡單的示範。Option模型實現在“Microsoft.Extensions.OptionModel”這個NuGet包中,除此之外,我們需要採用依賴注入的方式來使用Option模型,所以我們需要按照如下的方式為應用添加針對相應的依賴。
{ ... "dependencies": { "Microsoft.Extensions.OptionsModel" : "1.0.0-rc1-final", "Microsoft.Extensions.DependencyInjection" : "1.0.0-rc1-final" }, }
藉助於Option模型的自動綁定機制,我們無需再手工地讀取配置資訊,所以我們將FormatSettings、DateTimeFormatSettings和CurrencyDecimalSettings的建構函式刪除,只保留其屬性成員。在作為程式入口的Main方法中,我們採用如下的方式建立這個表示格式設定的FormatSettings對象。
class Program{ static void Main(string[] args) { Dictionary<string, string> source = new Dictionary<string, string> { ["Format:DateTime:LongDatePattern"] = "dddd, MMMM d, yyyy", ["Format:DateTime:LongTimePattern"] = "h:mm:ss tt", ["Format:DateTime:ShortDatePattern"] = "M/d/yyyy", ["Format:DateTime:ShortTimePattern"] = "h:mm tt", ["Format:CurrencyDecimal:Digits"] = "2", ["Format:CurrencyDecimal:Symbol"] = "$", }; IConfiguration configuration = new ConfigurationBuilder() .Add(new MemoryConfigurationProvider(source)) .Build() .GetSection("Format")); IOptions<FormatSettings> optionsAccessor = new ServiceCollection() .AddOptions() .Configure<FormatSettings>(configuration) .BuildServiceProvider() .GetService<IOptions<FormatSettings>>(); FormatSettings settings = optionsAccessor.Value; Console.WriteLine("DateTime:"); Console.WriteLine("\t{0,-16}: {1}", "LongDatePattern",settings.DateTime.LongDatePattern); Console.WriteLine("\t{0,-16}: {1}", "LongTimePattern",settings.DateTime.LongTimePattern); Console.WriteLine("\t{0,-16}: {1}", "ShortDatePattern",settings.DateTime.ShortDatePattern); Console.WriteLine("\t{0,-16}: {1}\n", "ShortTimePattern",settings.DateTime.ShortTimePattern); Console.WriteLine("CurrencyDecimal:"); Console.WriteLine("\t{0,-16}: {1}", "Digits",settings.CurrencyDecimal.Digits); Console.WriteLine("\t{0,-16}: {1}", "Symbol",settings.CurrencyDecimal.Symbol); } }
如上面的程式碼片段所示,我們建立一個ServiceCollection對象並調用擴充方法AddOptions註冊於針對Option模型的服務。接下來我們調用Configure方法將FormatSettings這個Option類型與對應的Configuration對象進行映射。我們最後利用這個ServiceCollection對象產生一個ServiceProvider,並調用其GetService方法得到一個類型為IOptions<FormatSettings>的對象,後者的Value屬性返回的就是綁定了相關配置的FormatSettings對象。
以上就是本文的全部內容,希望對大家的學習有所協助。