ASP.NET Core的配置(4):多樣性的配置來源[上篇]
較之傳統通過App.config和Web.config這兩個XML文件承載的配置系統,ASP.NET Core采用的這個全新的配置模型的最大一個優勢就是針對多種不同配置源的支持。我們可以將內存變量、命令行參數、環境變量和物理文件作為原始配置數據的來源,如果采用物理文件作為配置源,我們可以選擇不同的格式,比如XML、JSON和INI等。如果這些默認支持的配置源形式還不能滿足你的需求,我們還可以通過注冊自定義ConfigurationProvider的方式將其他形式數據作為我們的配置來源。接下來就讓我們來逐個認識一下配置模型原生提供的ConfigurationProvider。
目錄
MemoryConfigurationProvider
EnvironmentVariablesConfigurationProvider
CommandLineConfigurationProvider
JsonConfigurationProvider
XmlConfiguationProvider
IniConfigurationProvider
自定義ConfigurationProvider
一、MemoryConfigurationProvider
通過本章第2節對配置模型的介紹,我們知道ConfigurationProvider在配置模型中所起的作用就是讀取原始的配置數據并將其轉換成基于數據字典的物理結構。在所有的ConfigurationProvider類型中,MemoryConfigurationProvider最為簡單直接,因為它對應的配置源就是一個數據字典,根本不需要作任何的結構轉換。
MemoryConfigurationProvider定義在“Microsoft.Extensions.Configuration.Memory”命名空間下。。如下面的代碼片段所示,派生于基類ConfigurationProvider的MemoryConfigurationProvider同時實現了IEnumerable<KeyValuePair<string, string>>接口,所以它自身可以作為一個字典對象來使用。原始的配置數據可以在創建MemoryConfigurationProvider的時候作為構造函數的參數來指定,也可以通過調用Add方法逐個進行添加。
1: public class MemoryConfigurationProvider : ConfigurationProvider, IEnumerable<KeyValuePair<string, string>>
2: {
3: public MemoryConfigurationProvider();
4: public MemoryConfigurationProvider(IEnumerable<KeyValuePair<string, string>> initialData);
5:
6: public void Add(string key, string value);
7: public IEnumerator<KeyValuePair<string, string>> GetEnumerator();
8: }
在使用的時候,我們需要將MemoryConfigurationProvider對象注冊到ConfigurationBuilder之上。具體來說,我們可以像前面演示的實例一樣直接調用ConfigurationBuilder的Add方法,也可以調用如下所示的擴展方法AddInMemoryCollection。
1: public static class MemoryConfigurationExtensions
2: {
3: public static IConfigurationBuilder AddInMemoryCollection(this IConfigurationBuilder configurationBuilder);
4: public static IConfigurationBuilder AddInMemoryCollection(this IConfigurationBuilder configurationBuilder, IEnumerable<KeyValuePair<string, string>> initialData);
5: }
二、EnvironmentVariablesConfigurationProvider
顧名思義,環境變量就是描述當前執行環境并影響進程執行行為的變量。按照作用域的不同,我們將環境變量非常三類,它們分別針對當前系統、當前用戶和當前進程。系統和用戶級別的環境變量保存在注冊表中,其路徑分別為“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: public static IDictionary GetEnvironmentVariables();
6: public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target);
7:
8: public static void SetEnvironmentVariable(string variable, string value);
9: public static void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target);
10: }
11:
12: public enum EnvironmentVariableTarget
13: {
14: Process,
15: User,
16: Machine
17: }
環境變量的添加、修改和刪除均由SetEnvironmentVariable方法來實現,如果沒有顯式指定target參數,默認采用的是EnvironmentVariableTarget.Process。如果希望刪除指定名稱的環境變量,我們只需要在調用這個方法的時候將value參數設置為Null或者空字符串即可。
借助EnvironmentVariablesConfigurationProvider,我們可以將環境變量作為配置源。該類型定義在“Microsoft.Extensions.Configuration.EnvironmentVariables”程序集中,程序集的名稱同時也是所在NuGet包的名稱。如下面的代碼片段所示,EnvironmentVariablesConfigurationProvider具有兩個構造函數重載,如果調用默認無參構造函數,意味著我們會使用所有的環境變量。另一個構造函數提供了一個字符串類型的參數prefix,如果調用這個構造函數來創建一個EnvironmentVariablesConfigurationProvider,意味著我們只會使用名稱以此為前綴的環境變量。
1: public class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
2: {
3: public EnvironmentVariablesConfigurationProvider();
4: public EnvironmentVariablesConfigurationProvider(string prefix);
5: public override void Load();
6: }
由于作為原始配置數據的環境變量本身就是一個Key和Value均為字符串的數據字典,所以EnvironmentVariablesConfigurationProvider無需在進行結構轉換,所以當Load方法被執行之后,它只需要將符合條件篩選出來并添加到自己的配置字典中即可。有一點值得一提的是,如果我們在創建EnvironmentVariablesConfigurationProvider對象是指定了用于篩選環境變量的前綴,當符合條件的環境變量被添加到自身的配置字典之后,這個前綴也會從元素的Key中剔除。如下所示的代碼片段基本上體現了EnvironmentVariablesConfigurationProvider的實現邏輯。
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: }
也正是因為環境變量自身是數據字典,所以我們可以采用路徑化的變量名定義一組相關的環境變量來提供一個復雜對象、集合或者字典對象的配置數據。如下面的代碼片段所示,我們采用這樣的方式將綁定為一個Profile對象的基本信息定義成一組相關的環境變量。由于這組環境變量名稱具有相同的前綴“Profile”,所以我們利用這個前綴來創建一個 EnvironmentVariablesConfigurationProvider對象。在將它添加到ConfigurationBuilder之后,我們是用后者生成的Configuration對象采用配置綁定的方式得到一個Profile對象。
1: Environment.SetEnvironmentVariable("Profile:Gender", "Male");
2: Environment.SetEnvironmentVariable("Profile:Age", "18");
3: Environment.SetEnvironmentVariable("Profile:ContactInfo:Email", "foobar@outlook.com");
4: Environment.SetEnvironmentVariable("Profile:ContactInfo:PhoneNo", "123456789");
5:
6: Profile profile = new ConfigurationBuilder()
7: .Add(new EnvironmentVariablesConfigurationProvider("Profile:"))
8: .Build()
9: .Get<Profile>();
在使用EnvironmentVariablesConfigurationProvider的時候,我們可以按照上面演示的方式顯式地調用Add方法將創建的EnvironmentVariablesConfigurationProvider對象注冊到指定的ConfigurationBuilder對象之外,也可以直接調用如下所示的擴展方法AddEnvironmentVariables。
1: public static class EnvironmentVariablesExtensions
2: {
3: public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder);
4: public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder, string prefix);
5: }
三、CommandLineConfigurationProvider
在很多情況下,我們會采用Self-Host的方式將一個ASP.NET Core應用寄宿一個托管進程中,在這種情況下我們傾向于采用命令行的方式來啟動寄宿程序。當以命令行的形式啟動一個ASP.NET Core應用時,我們希望直接使用命名行開關(Switch)來控制應用的一些行為,所以命令行開關自然也就成為了配置常用的來源之一。配置模型針對這種配置源的支持是通過CommandLineConfigurationProvider來實現的,該類型定義在“Microsoft.Extensions.Configuration.CommandLine”程序集中,這也是所在NuGet包的名稱。
在以命令行的形式執行某個命令的時候,命令行開關(包括名稱和值)體現為一個簡單的字符串集合,所以CommandLineConfigurationProvider的根本目的在于將命名行開關從字符串集合的形式轉換成配置字典的形式。要充分理解這個轉換規則,我們先得來了解一下CommandLineConfigurationProvider支持的命令行開關究竟采用怎樣的形式來指定。我們通過一個簡單的實例來說明命令行開關的集中指定方式。假設我們有一個命令“exec”并采用如下所示的方式執行某個托管程序(app)。
1: exec app {options}
在執行這個命令的時候我們通過相應的命令行開關指定兩個選項,其中一個表示采用的CPUI架構(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
為了執行上的便利,很多命名行開關都具有縮寫的形式。以上述的這兩個命令行開關為例,我們可以采用首字母“a”和“r”來代表作為全名的“architecture”和“runtime”。如果采用縮寫的命令行開關名稱,那么我們就可以按照如下兩種方式指定CPU架構和運行時類型。
1: exec app –-a x64 –-r coreclr
2: exec app -a x64 -r coreclr
綜上所示,我們一共有五種指定命名行開關的方式,其中三種采用命令行開關的全名,余下的兩種則使用命令行開關的縮寫形式。這五種命名開關的指定形式所采用的原始參數以及縮寫與全名的映射關系。
在對命令行開關的集中指定形式具有基本了解之后,我們來認識一下將它們引入配置模型并作為配置源數據來源的CommandLineConfigurationProvider。如下面的代碼片斷所示,我們需要以字符串集合的形式指定原始的命令行參數來創建一個CommandLineConfigurationProvider對象,只讀屬性Args返回的也正是這個集合。
1: public class CommandLineConfigurationProvider : ConfigurationProvider
2: {
3: public CommandLineConfigurationProvider(IEnumerable<string> args, IDictionary<string, string> switchMappings = null);
4: public override void Load();
5:
6: protected IEnumerable<string> Args { get; }
7: }
構造函數另一個字典類型的參數switchMappings用于指定命令行開關名稱的縮寫形式與全名的映射關系。一個命令行開關可以包含多個不同的縮寫形式,比如“architecture”可以縮寫成“a”,也和縮寫成“arch”。如果采用縮寫形式,指定的命名行開關名稱必須以“-”或者“--”為前綴,那么這個switchMappings參數對應字典對象中的Key也需要采用相應的前綴。
在使用CommandLineConfigurationProvider的時候,我們可以直接創建這個對象并調用Add方法將其添加到指定的ConfigurationBuilder之中。我們可以直接調用ConfigurationBuilder對象具有如下定義的兩個擴展方法AddCommandLine達到相同的目的。
1: public static class CommandLineConfigurationExtensions
2: {
3: public static IConfigurationBuilder AddCommandLine(this IConfigurationBuilder configurationBuilder, string[] args);
4: public static IConfigurationBuilder AddCommandLine(this IConfigurationBuilder configurationBuilder, string[] args,IDictionary<string, string> switchMappings);
5: }
我們照例通過通過一個簡單的實例來演示如何利用CommandLineConfigurationProvider將命令行開關作為配置的原始來源。如下面的代碼片斷所示,在靜態方法GetConfigurations中,我們按照上面表格所示的五種方式創建了以命名行參數作為來源的Configuration對象。為了驗證這五種命名行開關指定形式的等效性,我們從中提取配置項“architecture”和“runtime”并驗證它們的值。
1: public class Program
2: {
3: public static void Main(string[] args)
4: {
5: foreach (IConfiguration configuration in GetConfigurations())
6: {
7: Debug.Assert(configuration["architecture"] == "x64");
8: Debug.Assert(configuration["runtime"] == "coreclr");
9: }
10: }
11:
12: private static IEnumerable<IConfiguration> GetConfigurations()
13: {
14: yield return new ConfigurationBuilder()
15: .AddCommandLine(new string[] { "/architecture", "x64", "/runtime", "coreclr" })
16: .Build();
17:
18: yield return new ConfigurationBuilder()
19: .AddCommandLine(new string[] { "--architecture", "x64", "--runtime", "coreclr" })
20: .Build();
21:
22: yield return new ConfigurationBuilder()
23: .AddCommandLine(new string[] { "architecture=x64", "runtime=coreclr" })
24: .Build();
25:
26: yield return new ConfigurationBuilder()
27: .AddCommandLine(new string[] { "--a", "x64", "--r", "coreclr" }, new Dictionary<string, string>
28: {
29: ["--a"] = "architecture",
30: ["--r"] = "runtime"
31: }).Build();
32:
33: yield return new ConfigurationBuilder()
34: .AddCommandLine(new string[] { "-a", "x64", "-r", "coreclr" }, new Dictionary<string, string>
35: {
36: ["-a"] = "architecture",
37: ["-r"] = "runtime"
38: }).Build();
39: }
40: }
考慮到命名行的使用場景,我們一般情況下只利用命令行開關來提供單一的配置項,很少將其邦定為一個結構化的Options對象。不過命名行開關雖然以字符串集合的形式體現,但是它們可以直接映射為配置字典,所以我們完全可以通過采用路徑化的命令行開關(比如“/foo:bar:baz abc”)來提供最終綁定為復雜對象設置集合和字典的配置源。