閱讀21 返回首頁    go 京東網上商城


.NET Core采用的全新配置係統[5]: 聊聊默認支持的各種配置源[內存變量,環境變量和命令行參數]

較之傳統通過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);


二、環境變量

顧名思義,環境變量就是描述當前執行環境並影響進程執行行為的變量。按照作用域的不同,我們將環境變量劃分成三類,即分別針對當前係統、當前用戶和當前進程的環境變量。係統和用戶級別的環境變量保存在注冊表中,其路徑分別為“”和“ ”。

環境變量提取和維護可以通過靜態類型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” )。

11

針對環境變量的配置源通過如下一個 EnvironmentVariablesConfigurationSource類型來表示,該類型定義在NuGet包“”之中。該類型指定義了一個字符串類型的屬性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打印出來。我們運行該程序後分別采用上述五種方式提供了命令行參數,根據如下所示的輸出結果,會發現解析命令行參數生成的配置是完全等效的。

image


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-25 11:33:58

  上一篇:go  便攜式可穿戴醫療電子市場現狀分析
  下一篇:go  客服客服唿叫中心係統的自動質檢功能如何大幅減少成本-米領通信