較之傳統通過App.config和Web.config這兩個XML文件承載的配置系統,.NET Core採用的這個全新的配置模型的最大一個優勢就是針對多種不同配置源的支持。我們可以將記憶體變數、命令行參數、環境變數和物理文件作為原始配置數據的來源,如果採用物理文件作為配置源,我們可以選擇不同的格式(比... ...
較之傳統通過App.config和Web.config這兩個XML文件承載的配置系統,.NET Core採用的這個全新的配置模型的最大一個優勢就是針對多種不同配置源的支持。我們可以將記憶體變數、命令行參數、環境變數和物理文件作為原始配置數據的來源,如果採用物理文件作為配置源,我們可以選擇不同的格式(比如XML、JSON和INI等) 。如果這些預設支持的配置源形式還不能滿足你的需求,我們還可以通過註冊自定義ConfigurationSource的方式將其他形式數據作為我們的配置來源。 [ 本文已經同步到《ASP.NET Core框架揭秘》之中]
目錄
一、記憶體變數
二、環境變數
三、命令行參數
一、記憶體變數
從本被系列第一篇開始到現在,我們所有的實例演示一直都在使用MemoryConfigurationSource這種類型的ConfigurationSource來提供原始的配置。我們知道MemoryConfigurationSource採用一個字典對象(具體來說應該是一個元素類型為KeyValuePair<string, string>的集合)作為存放原始配置數據的容器。作為一個ConfigurationSource,它總是通過創建某個對應的ConfigurationProvider來從事具體的配置數據讀取工作,那麼MemoryConfigurationSource會提供一個怎樣的ConfigurationProvider呢?
1: public class MemoryConfigurationSource : IConfigurationSource
2: {
3: public IEnumerable<KeyValuePair<string, string>> InitialData { get; set; }
4:
5: public IConfigurationProvider Build(IConfigurationBuilder builder)
6: {
7: return new MemoryConfigurationProvider(this);
8: }
9: }
上面給出的代碼片段體現了MemoryConfigurationSource的完整定義,我們可以看到它具有一個IEnumerable<KeyValuePair<string, string>>類型的屬性InitialData來存放初始的配置數據。從Build方法的實現可以看出,真正被它用來讀取原始配置數據的是一個MemoryConfigurationProvider類型的對象,該類型的定義如下麵的代碼片段所示。
1: public class MemoryConfigurationProvider : ConfigurationProvider, IEnumerable<KeyValuePair<string, string>>
2: {
3: public MemoryConfigurationProvider(MemoryConfigurationSource source);
4: public void Add(string key, string value);
5: public IEnumerator<KeyValuePair<string, string>> GetEnumerator();
6: IEnumerator IEnumerable.GetEnumerator();
7: }
從上面的代碼片段可以看出,MemoryConfigurationProvider派生於抽象類ConfigurationProvider,同時還實現了IEnumerable<KeyValuePair<string, string>>介面。我們知道ConfigurationProvider直接使用一個Dictionary<string, string>來保存配置數據,當我們根據一個MemoryConfigurationSource對象調用構造函數創建MemoryConfigurationProvider的時候,它只需要將通過InitiateData屬性保存的配置數據轉移到這個字典中即可。MemoryConfigurationProvider還定義了一個Add方法是我們可以在任何時候都可以向配置字典中添加一個新的配置項。
通過前面對配置模型的介紹,我們知道ConfigurationProvider在配置模型中所起的作用就是讀取原始的配置數據並將其轉換成配置字典。在所有的預定義的ConfigurationProvider類型中,MemoryConfigurationProvider最為簡單直接,因為它對應的配置源就是一個配置字典,所以根本不需要作任何的結構轉換。
在利用MemoryConfigurationSource生成配置的時候,我們需要將它註冊到ConfigurationBuilder之上。具體來說,我們可以像前面演示的實例一樣直接調用ConfigurationBuilder的Add方法,也可以調用如下所示的了兩個重載的擴展方法AddInMemoryCollection。
1: public static IConfigurationBuilder AddInMemoryCollection(this IConfigurationBuilder configurationBuilder);
2: public static IConfigurationBuilder AddInMemoryCollection(this IConfigurationBuilder configurationBuilder, IEnumerable<KeyValuePair<string, string>> initialData);
二、環境變數
顧名思義,環境變數就是描述當前執行環境並影響進程執行行為的變數。按照作用域的不同,我們將環境變數劃分成三類,即分別針對當前系統、當前用戶和當前進程的環境變數。系統和用戶級別的環境變數保存在註冊表中,其路徑分別為“HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment”和“HKEY_CURRENT_USER\Environment ”。
環境變數提取和維護可以通過靜態類型Environment來實現。具體來說,我們可以調用它的靜態方法GetEnvironmentVariable方法獲得某個指定名稱的環境變數的值,而GetEnvironmentVariables方法則會將返回所有的環境變數,EnvironmentVariableTarget枚舉類型的參數代表環境變數作用域決定的存儲位置。如果在調用GetEnvironmentVariable或者GetEnvironmentVariables方法師沒有顯式指定target參數或者將參數指定為EnvironmentVariableTarget.Process,在進程初始化前存在的所有環境變數(包括針對系統、當前用戶和當前進程)將會作為候選列表。
1: public static class Environment
2: {
3: public static string GetEnvironmentVariable(string variable);
4: public static string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target);
5:
6: public static IDictionary GetEnvironmentVariables();
7: public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target);
8:
9: public static void SetEnvironmentVariable(string variable, string value);
10: public static void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target);
11: }
12:
13: public enum EnvironmentVariableTarget
14: {
15: Process,
16: User,
17: Machine
18: }
環境變數的添加、修改和刪除均由SetEnvironmentVariable方法來完成,如果沒有顯式指定target參數,預設採用的是EnvironmentVariableTarget.Process。如果希望刪除指定名稱的環境變數,我們只需要在調用這個方法的時候將value參數設置為Null或者空字元串即可。
除了在程式中利用靜態類型Environment,我們還可以執行命令行的方式查看和設置環境變數。除此之外,我們還可以利用“系統屬性(System Properties)”設置工具以可視化的方式查看和設置系統和用戶級別的環境變數(“This PC”>“Properties”>“Change Settings”>“Advanced”>“Environment Variables”)。如果採用Visual Studio 2015來調試我們編寫的應用,我們可以設置項目屬性的方式來設置進程級別的環境變數( “Properties” > “Debug”> “Environment Variables” )。
針對環境變數的配置源通過如下一個 EnvironmentVariablesConfigurationSource類型來表示,該類型定義在NuGet包“Microsoft.Extensions.Configuration.EnvironmentVariables”之中。該類型指定義了一個字元串類型的屬性Prefix,它表示用於篩選環境變數採用的首碼,也就是說如果我們設置了這個Prefix屬性,只會選擇名稱以此作為首碼的環境變數。
1: public class EnvironmentVariablesConfigurationSource : IConfigurationSource
2: {
3: public string Prefix { get; set; }
4: public IConfigurationProvider Build(IConfigurationBuilder builder)
5: {
6: return new EnvironmentVariablesConfigurationProvider(this.Prefix);
7: }
8: }
通過上面給出的代碼片段我們可以看出EnvironmentVariablesConfigurationSource會利用對應的EnvironmentVariablesConfigurationProvider來完成對環境變數的讀取工作。如下所示的代碼基本體現了EnvironmentVariablesConfigurationProvider的定義。由於作為原始配置數據的環境變數本身就是一個Key和Value均為字元串的數據字典,所以EnvironmentVariablesConfigurationProvider無需在進行結構轉換,所以當Load方法被執行之後,它只需要將符合條件篩選出來並添加到自己的配置字典中即可。值得一提的是,如果我們在創建EnvironmentVariablesConfigurationProvider對象是指定了用於篩選環境變數的首碼,當符合條件的環境變數被添加到自身的配置字典之後,這個首碼也會從元素的Key中剔除。這個細節也體現在上面定義的Load方法中。
1: public class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
2: {
3: private readonly string prefix;
4:
5: public EnvironmentVariablesConfigurationProvider(string prefix = null)
6: {
7: this.prefix = prefix ?? string.Empty;
8: }
9:
10: public override void Load()
11: {
12: var dictionary = Environment.GetEnvironmentVariables()
13: .Cast<DictionaryEntry>()
14: .Where(it => it.Key.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
15: .ToDictionary(it => it.Key.ToString().Substring(prefix.Length), it => it.Value.ToString());
16: this.Data = new Dictionary<string, string>(dictionary, StringComparer.OrdinalIgnoreCase);
17: }
18: }
在使用EnvironmentVariablesConfigurationSource的時候,我們可以調用Add方法將它註冊到指定的ConfigurationBuilder對象上。除此之外,EnvironmentVariablesConfigurationSource的中註冊還可以直接調用IConfigurationBuilder介面的如下兩個重載的擴展方法AddEnvironmentVariables來完成。
1: public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder);
2: public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder, string prefix);
我們照例編寫一個簡單的實例來演示如何採用環境變數作為配置源。如下麵的代碼片段所示,我們調用Environment的靜態方法SetEnvironment方法設置了四個環境變數,變數名稱具有相同的首碼“TEST_”。我們調用方法AddEnvironmentVariables創建一個EnvironmentVariablesConfigurationSource對象並將其註冊到創建的ConfigurationBuilder之上。調用AddEnvironmentVariables方法是我們將環境變數名稱首碼“TEST_” 作為參數。後續的代碼我們已經很熟悉了,即採用Options模式讀取環境變數並綁定為一個Profile對象。
1: Environment.SetEnvironmentVariable("TEST_gender", "Male");
2: Environment.SetEnvironmentVariable("TEST_age", "18");
3: Environment.SetEnvironmentVariable("TEST_contactInfo:emailAddress", "[email protected]");
4: Environment.SetEnvironmentVariable("TEST_contactInfo:PhoneNo", "123456789");
5:
6: IConfiguration config = new ConfigurationBuilder()
7: .AddEnvironmentVariables("TEST_")
8: .Build();
9:
10: Profile profile = new ServiceCollection()
11: .AddOptions()
12: .Configure<Profile>(config)
13: .BuildServiceProvider()
14: .GetService<IOptions<Profile>>()
15: .Value;
三、命令行參數
在很多情況下,我們會採用Self-Host的方式將一個ASP.NET Core應用寄宿一個托管進程中,在這種情況下我們傾向於採用命令行的方式來啟動寄宿程式。當以命令行的形式啟動一個ASP.NET Core應用時,我們希望直接使用命名行開關(Switch)來控制應用的一些行為,所以命令行開關自然也就成為了配置常用的來源之一。配置模型針對這種配置源的支持是通過CommandLineConfigurationSource來實現的,該類型定義在NuGet包 “Microsoft.Extensions.Configuration.CommandLine”中。
在以命令行的形式執行某個命令的時候,命令行開關(包括名稱和值)體現為一個簡單的字元串集合,所以CommandLineConfigurationSource的根本目的在於將命名行開關從字元串數組轉換成配置字典。要充分理解這個轉換規則,我們先得來瞭解一下CommandLineConfigurationSource支持的命令行開關究竟採用怎樣的形式來指定。我們通過一個簡單的實例來說明命令行開關的集中指定方式。假設我們有一個命令“exec”並採用如下所示的方式執行某個托管程式(app)。
1: exec app {options}
在執行這個命令的時候我們通過相應的命令行開關指定兩個選項,其中一個表示採用的CPU架構(X86或者X64),另一個表示運行時類型(CLR或者CoreCLR),我們將這兩個命令行開關分別命名為architecture和runtime。在執行命名行的時候,我們可以採用如下三種不同的方式指定這兩個命名行開關。
1: exec app /architecture x64 /runtime coreclr
2: exec app --architecture x64 --runtime coreclr
3: exec app architecture=x64 architecture=coreclr
為了執行上的便利,很多命名行開關都具有縮寫的形式,命令行開關的全名和縮寫之間具有一個映射關係(Switch Mapping)。以上述的這兩個命令行開關為例,我們可以採用首字母“a”和“r”來代表作為全名的“architecture”和“runtime”。如果採用縮寫的命令行開關名稱,那麼我們就可以按照如下兩種方式指定CPU架構和運行時類型。
1: exec app –-a x64 –-r coreclr
2: exec app -a x64 -r coreclr
綜上所示,我們一共有五種指定命名行開關的方式,其中三種採用命令行開關的全名,餘下的兩種則使用命令行開關的縮寫形式。下表總結了這五種命名開關的指定形式所採用的原始參數以及縮寫與全名的映射關係。這裡隱藏著一個重要的細節,字元 “-” 只能以縮寫的形式指定命令行開關的指,但是 “--” 則支持全稱和縮寫形式。
Arguments |
Switch Mapping |
/architecture x64 /runtime coreclr |
- |
--architecture x64 --runtime coreclr |
- |
architecture=x64 runtime=coreclr |
- |
--a x64 --r coreclr |
--a: architecture, --r: runtime |
-a x64 -r coreclr |
-a: architecture, -r: runtime |
原始的命令行參數總是體現為一個字元串數組, CommandLineConfigurationSource以字元串數組作為配置源,並利用對應的ConfigurationProvider將它轉換成配置字典。如下麵的代碼片斷所示,CommandLineConfigurationSource具有Args和SwitchMappings,前者正式代表承載著原始命令行參數的字元串數組,後者則保存了命令行開關的縮寫與全稱之間的映射關係。在實現的Build方法中,它根據這兩個屬性創建出一個CommandLineConfigurationProvider對象。
1: public class CommandLineConfigurationSource : IConfigurationSource