[ASP.NET Core 3框架揭秘] 配置[6]:多樣化的配置源[上篇]


.NET Core采用的這個全新的配置模型的一個主要的特點就是對多種不同配置源的支持。我們可以將內存變量、命令行參數、環境變量和物理文件作為原始配置數據的來源。如果采用物理文件作為配置源,我們可以選擇不同的格式(比如XML、JSON和INI等)。如果這些默認支持的配置源形式還不能滿足你的需求,我們還可以通過注冊自定義IConfigurationSource的方式將其他形式數據作為配置來源。

一、MemoryConfigurationSource

在之前的實例演示都在使用MemoryConfigurationSource來提供原始的配置。我們知道MemoryConfigurationSource配置源采用一個字典對象(具體來說應該是一個元素類型為KeyValuePair<string, string>的集合)作為存放原始配置數據的容器。作為一個IConfigurationSource對象,它總是通過創建某個對應的IConfigurationProvider對象來完成具體的配置數據讀取工作,那么MemoryConfigurationSource會提供一個怎樣的IConfigurationProvider呢?

public class MemoryConfigurationSource : IConfigurationSource
{
    public IEnumerable<KeyValuePair<string, string>> InitialData { get; set; }
    public IConfigurationProvider Build(IConfigurationBuilder builder) => new MemoryConfigurationProvider(this);
}

上面給出的代碼片段體現了MemoryConfigurationSource的完整定義,我們可以看到它具有一個IEnumerable<KeyValuePair<string, string>>類型的屬性InitialData來存放初始的配置數據。從Build方法的實現可以看出,真正被它用來讀取原始配置數據的是一個MemoryConfigurationProvider類型的對象,該類型的定義如下面的代碼片段所示。

public class MemoryConfigurationProvider : ConfigurationProvider,  IEnumerable<KeyValuePair<string, string>>
{
    public MemoryConfigurationProvider(MemoryConfigurationSource source);
    public void Add(string key, string value);
    public IEnumerator<KeyValuePair<string, string>> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();
}

從上面的代碼片段可以看出,MemoryConfigurationProvider派生於抽象類ConfigurationProvider,同時還實現了IEnumerable<KeyValuePair<string, string>>接口。我們知道ConfigurationProvider對象直接使用一個Dictionary<string, string>來保存配置數據,當我們根據一個MemoryConfigurationSource對象調用構造函數創建MemoryConfigurationProvider的時候,它只需要將通過InitialData屬性保存的配置數據轉移到這個字典中即可。MemoryConfigurationProvider還定義了一個Add方法使我們可以在任何時候都可以向配置字典中添加一個新的配置項。

通過前面對配置模型的介紹,我們知道IConfigurationProvider對象在配置模型中所起的作用就是讀取原始的配置數據並將其轉換成配置字典。在所有的預定義的IConfigurationProvider實現類型中,MemoryConfigurationProvider最為簡單直接,因為它對應的配置源就是一個配置字典,所以根本不需要作任何的結構轉換。

在利用MemoryConfigurationSource生成配置的時候,我們需要將它注冊到IConfigurationBuilder對象之上。具體來說,我們可以像前面演示的實例一樣直接調用IConfigurationBuilder接口的Add方法,也可以調用如下所示的兩個重載的AddInMemoryCollection擴展方法。

public static class MemoryConfigurationBuilderExtensions
{
    public static IConfigurationBuilder AddInMemoryCollection(  this IConfigurationBuilder configurationBuilder);
    public static IConfigurationBuilder AddInMemoryCollection(  this IConfigurationBuilder configurationBuilder,   IEnumerable<KeyValuePair<string, string>> initialData);
}

二、EnvironmentVariablesConfigurationSource

顧名思義,環境變量就是描述當前執行環境並影響進程執行行為的變量。按照作用域的不同,我們將環境變量划分成三類,即分別針對當前系統、當前用戶和當前進程的環境變量。對於Windows系統來說,系統和用戶級別的環境變量保存在注冊表中,其路徑分別為“HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment”和“HKEY_CURRENT_USER\Environment ”。

環境變量的提取和維護可以通過靜態類型Environment來完成。具體來說,我們可以調用它的靜態方法GetEnvironmentVariable獲得某個指定名稱的環境變量的值,而GetEnvironmentVariables方法則會返回所有的環境變量,EnvironmentVariableTarget枚舉類型的參數代表環境變量作用域決定的存儲位置。如果在調用GetEnvironmentVariable或者GetEnvironmentVariables方法時沒有顯式指定target參數或者將參數指定為EnvironmentVariableTarget.Process,在進程初始化前存在的所有環境變量(包括針對系統、當前用戶和當前進程)將會作為候選列表。

public static class Environment
{
    public static string GetEnvironmentVariable(string variable);
    public static string GetEnvironmentVariable(string variable,  EnvironmentVariableTarget target);

    public static IDictionary GetEnvironmentVariables();
    public static IDictionary GetEnvironmentVariables( EnvironmentVariableTarget target);

    public static void SetEnvironmentVariable(string variable, string value);
    public static void SetEnvironmentVariable(string variable, string value,  EnvironmentVariableTarget target);
}

public enum EnvironmentVariableTarget
{
      Process,
      User,
      Machine
}

環境變量的添加、修改和刪除均由SetEnvironmentVariable方法來完成,如果沒有顯式指定target參數,默認采用的是EnvironmentVariableTarget.Process。如果希望刪除指定名稱的環境變量,我們只需要在調用這個方法的時候將value參數設置為Null或者空字符串即可。

除了在程序中利用靜態類型Environment,我們還可以采用命令行的方式查看和設置環境變量。除此之外,我們在開發環境中還可以利用“系統屬性(System Properties)”設置工具以可視化的方式查看和設置系統和用戶級別的環境變量(“This PC”>“Properties”>“Change Settings”>“Advanced”>“Environment Variables”)。如果采用Visual Studio 來調試我們編寫的應用,我們可以采用設置項目屬性的方式來設置進程級別的環境變量(“Properties” > “Debug”> “Environment Variables” )。在第1章 “全新的開發體驗” 中我們提到過,設置的環境變量會被保存到launchSettings.json文件中。

6-16、

針對環境變量的配置源通過如下這個 EnvironmentVariablesConfigurationSource類型來表示,該類型定義在NuGet包“Microsoft.Extensions.Configuration.EnvironmentVariables”之中。該類型定義了一個字符串類型的屬性Prefix,它表示環境變量名的前綴。如果我們設置了這個Prefix屬性,系統只會選擇名稱以此作為前綴的環境變量。

public class EnvironmentVariablesConfigurationSource : IConfigurationSource
{
    public string Prefix { get; set; }
    public IConfigurationProvider Build(IConfigurationBuilder builder)=> new EnvironmentVariablesConfigurationProvider(Prefix);
}

通過前面給出的代碼片段我們可以看出EnvironmentVariablesConfigurationSource配置源會利用對應的EnvironmentVariablesConfigurationProvider對象來讀取環境變量,此操作體現在如下所示的Load方法中。由於環境變量本身就是一個數據字典,所以EnvironmentVariables
ConfigurationProvider對象無需再進行結構上的轉換。當Load方法被執行之后,它只需要將符合條件的環境變量篩選出來並添加到自己的配置字典中即可。

public class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
{
    private readonly string _prefix;
    public EnvironmentVariablesConfigurationProvider(string prefix = null) =>  _prefix = prefix ?? string.Empty;
    public override void Load()
    {
        var dictionary = Environment.GetEnvironmentVariables()
            .Cast<DictionaryEntry>()
            .Where(it => it.Key.ToString().StartsWith( _prefix, StringComparison.OrdinalIgnoreCase))
            .ToDictionary(it => it.Key.ToString().Substring(_prefix.Length),   it => it.Value.ToString());
        Data = new Dictionary<string, string>( dictionary, StringComparer.OrdinalIgnoreCase);
    }
}

值得一提的是,如果我們在創建EnvironmentVariablesConfigurationProvider對象時指定了用於篩選環境變量的前綴,當符合條件的環境變量被添加到自身的配置字典之后,配置項的名稱會將此前綴剔除。比如前綴設置為“FOO_”,環境變量“FOO_BAR”被添加到配置字典之后,配置項 名稱會變成“BAR”,這個細節也體現在上面定義的Load方法中。

在使用EnvironmentVariablesConfigurationSource的時候,我們可以調用Add方法將它注冊到指定的IConfigurationBuilder對象上。除此之外,EnvironmentVariablesConfigurationSource的注冊還可以直接調用IConfigurationBuilder接口的如下三個重載的擴展方法AddEnvironmentVariables來完成。

public static class EnvironmentVariablesExtensions
{
    public static IConfigurationBuilder AddEnvironmentVariables(  this IConfigurationBuilder configurationBuilder);
    public static IConfigurationBuilder AddEnvironmentVariables(  this IConfigurationBuilder builder,  Action<EnvironmentVariablesConfigurationSource> configureSource);
    public static IConfigurationBuilder AddEnvironmentVariables(  this IConfigurationBuilder configurationBuilder, string prefix);
}

我們照例編寫一個簡單的實例來演示如何利用環境變量作為配置源。如下面的代碼片段所示,我們調用Environment的靜態方法SetEnvironmentVariable方法設置了四個環境變量,變量名稱具有相同的前綴TEST_。我們調用方法AddEnvironmentVariables創建一個Environment
VariablesConfigurationSource對象並將其注冊到創建的ConfigurationBuilder 之上,在調用該方法時我們將環境變量名稱前綴 設置為 “TEST_”。我們最終將由ConfigurationBuilder構建出的IConfiguration對象綁定成一個Profile對象。

public class Program
{
    public static void Main()
    {
        Environment.SetEnvironmentVariable("TEST_GENDER", "Male");
        Environment.SetEnvironmentVariable("TEST_AGE", "18");
        Environment.SetEnvironmentVariable("TEST_CONTACTINFO:EMAILADDRESS", "foobar@outlook.com");
        Environment.SetEnvironmentVariable("TEST_CONTACTINFO:PHONENO", "123456789");

        var profile = new ConfigurationBuilder()
            .AddEnvironmentVariables("TEST_")
            .Build()
            .Get<Profile>();

        Debug.Assert(profile.Equals(new Profile(Gender.Male, 18, "foobar@outlook.com", "123456789")));
    }
}

三、CommandLineConfigurationSource對象

在很多情況下,我們會采用Self-Host的方式將一個ASP.NET Core應用寄宿到一個托管進程中,在這種情況下我們傾向於采用命令行的方式來啟動寄宿程序。當以命令行的形式啟動一個ASP.NET Core應用時,我們希望直接使用命名行開關(Switch)來控制應用的一些行為,所以命令行開關自然也就成為了配置常用的來源之一。配置模型針對這種配置源的支持是通過CommandLineConfigurationSource來實現的,該類型定義在NuGet包 “Microsoft.Extensions.Configuration.CommandLine”中。

在以命令行的形式執行某個命令的時候,命令行開關(包括名稱和值)體現為一個簡單的字符串數組,所以CommandLineConfigurationSource的根本目的在於將命名行開關從字符串數組轉換成配置字典。要充分理解這個轉換規則,我們先得來了解一下CommandLine
ConfigurationSource支持的命令行開關究竟采用怎樣的形式來指定。我們通過一個簡單的實例來說明命令行開關的幾種指定方式。假設我們有一個命令“exec”並采用如下所示的方式執行某個托管程序(app)。

exec app {options}

在執行這個命令的時候我們通過相應的命令行開關指定多個選項。總的來說,命令行開關的指定形式大體上分為兩種,我將它們稱為“單參數(Single Argument)”和“雙參數(Double Arguments)”。所謂單參數形式就是采用等號(“=”)將命令行開關的名稱和值通過如下方法采用一個參數來指定。

{name}={value}
{prefix}{name}={value}

對於第二種單參數命令行開關的指定形式,我們可以在開關名稱前面添加一個前綴,目前的前綴支持“/”、“--”和“-”三種。遵循這樣的格式,我們可以采用如下三種方式將命令行開關architecture設置為“x64”。下面的列表之所以沒有使用“-”前綴,是因為這個前綴要求使用“命令行開關映射(Switch Mapping)”,我們稍后會對此作單獨介紹。

exec app architecture=x64
exec app /architecture=x64
exec app --architecture=x64

除了采用單參數形式,我們還可以采用雙參數形式來指定命令行開關,所謂的“雙參數”就是使用兩個參數分別定義命令行開關的名稱和值。這種形式采用的具體格式為“{prefix}{name} {value}”,所以上述的這個命令行開關architecture也可以采用如下的方式來指定。

exec app /architecture x64
exec app –-architecture x64

命令行開關的全名和縮寫之間具有一個映射關系(Switch Mapping)。以上述的這兩個命令行開關為例,我們可以采用首字母“a”來代替“architecture”。如果使用“-”作為前綴,不論采用單參數還是雙參數形式,都必須使用映射后的開關名稱。值得一提的是,同一個命令行開關可以具有多個映射,比如我們也可以同時將“architecture”映射為“arch”。假設“architecture”具有了這兩種映射,我們就可以按照如下兩種方式指定CPU架構。

exec app -a=x64
exec app -arch=x64
exec app -a x64
exec app -arch x64

在了解了命令行開關的指定形式之后,我們接着來說說CommandLineConfigurationSource類型和由它提供的CommandLineConfigurationProvider。由於原始的命令行參數總是體現為一個采用空格分隔的字符串,這樣的字符串可以進一步轉換成一個字符串集合,所以CommandLineConfigurationSource對象以字符串集合作為配置源。如下面的代碼片斷所示,CommandLineConfigurationSource類型具有Args和SwitchMappings兩個屬性,前者代表承載着原始命令行參數的字符串集合,后者則保存了命令行開關的縮寫與全稱之間的映射關系。CommandLineConfigurationSource實現 的Build方法會根據這兩個屬性創建並返回一個CommandLineConfigurationProvider對象。

public class CommandLineConfigurationSource : IConfigurationSource
{
    public IEnumerable<string> Args { get; set; }
    public IDictionary<string, string> SwitchMappings { get; set; }

    public IConfigurationProvider Build(IConfigurationBuilder builder) => new CommandLineConfigurationProvider( Args,SwitchMappings); 
}

具有如下定義的CommandLineConfigurationProvider對象依然是抽象類ConfigurationProvider的繼承者。CommandLineConfigurationProvider對象的目的很明確,就是對體現為字符串集合的原始命令行參數進行解析,並將解析出來的參數名稱和值添加到配置字典中 ,這一切都是在重寫的Load方法中完成的。

public class CommandLineConfigurationProvider : ConfigurationProvider
{
    protected IEnumerable<string> Args { get; }
    public CommandLineConfigurationProvider(IEnumerable<string> args,  IDictionary<string, string> switchMappings = null);
    public override void Load();
}

在采用基於命令行參數作為配置源的時候,我們可以創建一個CommandLineConfigurationSource並將其注冊到ConfigurationBuilder上。我們也可以調用IConfigurationBuilder接口的如下三個擴展方法AddCommandLine將兩個步驟合二為一。

public static class CommandLineConfigurationExtensions
{
    public static IConfigurationBuilder AddCommandLine(  this IConfigurationBuilder builder,  Action<CommandLineConfigurationSource> configureSource);
    public static IConfigurationBuilder AddCommandLine(  this IConfigurationBuilder configurationBuilder, string[] args);
    public static IConfigurationBuilder AddCommandLine(  this IConfigurationBuilder configurationBuilder, string[] args,  IDictionary<string, string> switchMappings);
}

為了讓讀者朋友們對CommandLineConfigurationSource/CommandLineConfigurationProvider解析命令行參數采用的策略有一個深刻的認識,我們來演示一個簡單的實例。如下面的代碼片段所示,我們創建了一個ConfigurationBuilder對象並調用AddCommandLine方法注冊了針對命令行參數的配置源,Main方法的參數args直接作為原始的命令行參數。

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var mapping = new Dictionary<string, string>
            {
                ["-a"]     = "architecture",
                ["-arch"]     = "architecture"
            };
            var configuration = new ConfigurationBuilder()
                .AddCommandLine(args, mapping)
                .Build();
            Console.WriteLine($"Architecture: {configuration["architecture"]}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

在調用擴展方法AddCommandLine注冊CommandLineConfigurationSource的時候,我們指定了一個命令行開關映射表,它將命令行開關 “architecture” 映射為 “a” 和 “arch” 。需要注意的是,在通過字典定義命令行開關映射的時候,作為目標名稱的Key應該添加 “-” 前綴。接下來我們調用ConfigurationBuilder的Build方法創建出IConfiguration對象,並從中提取出 “architecture” 配置項的值並打印出來。如下圖所示,我們采用命令行的形式啟動這個程序並以不同的形式指定 “architecture” 的值。

6-17

[ASP.NET Core 3框架揭秘] 配置[1]:讀取配置數據[上篇]
[ASP.NET Core 3框架揭秘] 配置[2]:讀取配置數據[下篇]
[ASP.NET Core 3框架揭秘] 配置[3]:配置模型總體設計
[ASP.NET Core 3框架揭秘] 配置[4]:將配置綁定為對象
[ASP.NET Core 3框架揭秘] 配置[5]:配置數據與數據源的實時同步
[ASP.NET Core 3框架揭秘] 配置[6]:多樣化的配置源[上篇]
[ASP.NET Core 3框架揭秘] 配置[7]:多樣化的配置源[中篇]
[ASP.NET Core 3框架揭秘] 配置[8]:多樣化的配置源[下篇]
[ASP.NET Core 3框架揭秘] 配置[9]:自定義配置源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM