較之傳統通過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", "foobar@outlook.com");
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
2: {
3: public IEnumerable<string> Args { get; set; }
4: public IDictionary<string, string> SwitchMappings { get; set; }
5:
6: public IConfigurationProvider Build(IConfigurationBuilder builder)
7: {
8: return new CommandLineConfigurationProvider(this.Args, this.SwitchMappings);
9: }
10: }
具有如下定義的CommandLineConfigurationProvider依然是抽象類ConfigurationProvider的繼承者。它的目的很明確,就是對體現為字符串數組的原始命令行參數進行解析,并將解析出來參數名稱和值添加到配置字典中 。這一切都是在重寫的Load方法中完成的。
1: public class CommandLineConfigurationProvider : ConfigurationProvider
2: {
3: protected IEnumerable<string> Args { get; }
4: public CommandLineConfigurationProvider(IEnumerable<string> args, IDictionary<string, string> switchMappings = null);
5: public override void Load();
6: }
在采用基于命令行參數作為配置源的時候,我們可以創建一個CommandLineConfigurationSource并將其注冊到ConfigurationBuilder之上。我們也可以調用IConfigurationBuilder接口的如下兩個擴展方法AddCommandLine將兩個步驟合二為一。
1: public static IConfigurationBuilder AddCommandLine(this IConfigurationBuilder configurationBuilder, string[] args);
2: public static IConfigurationBuilder AddCommandLine(this IConfigurationBuilder configurationBuilder, string[] args, IDictionary<string, string> switchMappings);
為了讓讀者朋友們對CommandLineConfigurationProvider解析命令行參數采用的策略具有一個深刻的認識,我們來演示一個簡單的實例。我們創建一個控制臺應用,并添加針對 “Microsoft.Extensions.Configuration.CommandLine”這個NuGet包的依賴。在Main方法中,我們編寫了如下一段簡單的實例程序。
1: while (true)
2: {
3: try
4: {
5: Console.Write("Enter command line switches:");
6: string arguments = Console.ReadLine();
7: Dictionary<string, string> mapping = new Dictionary<string, string>
8: {
9: ["--a"] = "architecture ",
10: ["-a"] = "architecture ",
11: ["--r"] = "runtime",
12: ["-r"] = "runtime",
13: };
14: IConfiguration config = new ConfigurationBuilder()
15: .AddCommandLine(arguments.Split(' '), mapping)
16: .Build();
17:
18: foreach (var section in config.GetChildren())
19: {
20: Console.WriteLine($"{section.Key}: {section.Value}");
21: }
22: }
23: catch(Exception ex)
24: {
25: Console.WriteLine(ex.Message);
26: }
27: }
如上面的代碼片斷所示,我們在一個無限循環中接收用戶指定的命令行參數,并據此創建一個CommandLineConfigurationSource對象并將其注冊到ConfigurationBuilder之上。我們在創建這個CommandLineConfigurationSource對象的時候,還指定一個表示命令行開關映射關系的字典。接下來我們利用這個ConfigurationBuilder生成一個Configuration對象,并將其所有子配置節的Key和Value打印出來。我們運行該程序后分別采用上述五種方式提供了命令行參數,根據如下所示的輸出結果,會發現解析命令行參數生成的配置是完全等效的。
文章列表