也正是因为环境变量自身是数据字典,所以我们可以采用路径化的变量名定义一组相关的环境变量来提供一个复杂对象、集合或者字典对象的配置数据。如下面的代码片段所示,我们采用这样的方式将绑定为一个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”)来提供终绑定为复杂对象设置集合和字典的配置源。