.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文件中。

针对环境变量的配置源通过如下这个 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", "");
Environment.SetEnvironmentVariable("TEST_CONTACTINFO:EMAILADDRESS", "foobar@outlook.com");
Environment.SetEnvironmentVariable("TEST_CONTACTINFO:PHONENO", ""); var profile = new ConfigurationBuilder()
.AddEnvironmentVariables("TEST_")
.Build()
.Get<Profile>(); Debug.Assert(profile.Equals(new Profile(Gender.Male, , "foobar@outlook.com", "")));
}
}

三、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” 的值。

[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]:自定义配置源

[ASP.NET Core 3框架揭秘] 配置[6]:多样化的配置源[上篇]的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

  2. ASP.NET Core 6框架揭秘实例演示[09]:配置绑定

    我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...

  3. ASP.NET Core 6框架揭秘实例演示[25]:配置与承载环境的应用

    与服务注册一样,针对配置的设置同样可以采用三种不同的编程模式.第一种是利用WebApplicationBuilder的Host属性返回的IHostBuilder对象,它可以帮助我们设置面向宿主和应用的 ...

  4. [ASP.NET Core 3框架揭秘] 配置[1]:读取配置数据[上篇]

    提到"配置"二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...

  5. [ASP.NET Core 3框架揭秘] 配置[2]:读取配置数据[下篇]

    [接上篇]提到“配置”二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义 ...

  6. [ASP.NET Core 3框架揭秘] 配置[5]:配置数据与数据源的实时同步

    在<配置模型总体设计>介绍配置模型核心对象的时候,我们刻意回避了与配置同步相关的API,现在我们利用一个独立文章来专门讨论这个话题.配置的同步涉及到两个方面:第一,对原始的配置源实施监控并 ...

  7. [ASP.NET Core 3框架揭秘] 配置[3]:配置模型总体设计

    在<读取配置数据>([上篇],[下篇])上面一节中,我们通过实例的方式演示了几种典型的配置读取方式,接下来我们从设计的维度来重写认识配置模型.配置的编程模型涉及到三个核心对象,分别通过三个 ...

  8. [ASP.NET Core 3框架揭秘] 配置[7]:多样化的配置源[中篇]

    物理文件是我们最常用到的原始配置载体,而最佳的配置文件格式主要有三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...

  9. [ASP.NET Core 3框架揭秘] 配置[4]:将配置绑定为对象

    虽然应用程序可以直接利用通过IConfigurationBuilder对象创建的IConfiguration对象来提取配置数据,但是我们更倾向于将其转换成一个POCO对象,以面向对象的方式来使用配置, ...

随机推荐

  1. 五年老Android,我决定转后端开发了!

    今天给大家分享一些移动端(Android)开发学习后端开发(Java Web)的一些事儿,不知道从什么时候开始身边的同事都开始陆陆续续的在朋友圈发一些后端的文章如:Nginx.Docker.k8s类的 ...

  2. Vue过渡动画运用transition

    vue的过渡动画,主要是transition标签的使用,配合css动画实现的.官方文档css过渡 通过点击事件来切换show的值来改变显示的文本,下面的css通过进入离开时的在匀速状态下xxs(秒)下 ...

  3. [Odoo12基础教程]之开发过程中可能出现的问题

    可能出现的问题 更改代码后无变化 当你对代码进行更改之后,发现页面并没有变化,那么请尝试依次以下几种办法: 1.重启项目: 2.升级模块: 3.在开发者模式下刷新本地模块列表: 4.给data列表添加 ...

  4. 新闻实时分析系统-Kafka分布式集群部署

    Kafka是由LinkedIn开发的一个分布式的消息系统,使用Scala编写,它以可水平扩展和高吞吐率而被广泛使用.目前越来越多的开源分布式处理系统如Cloudera.Apache Storm.Spa ...

  5. es6 every的使用

    let arr2 =[1,3,5,7,9,10]; //arr2.every() 数组里面所有的元素都有符合条件,才返回true var b =arr2.every(function (val,ind ...

  6. mac系统下docker安装配置mysql详细步骤

    上文介绍了MacOS安装Docker傻瓜式教程,安装好后第一件事就决定把本地数据库迁移过来,那么首先就得安装mysql,下面就开始我们的安装之旅吧. 一.docker配置镜像加速器 我们使用docke ...

  7. wincap linux部署

    1.4.1 linux下安装Winpcap a) 下载Winpcap的源码:https://www.winpcap.org/devel.htm b) 上传源码包“WpcapSrc_4_1_3.zip” ...

  8. Debug 利器:pstack & strace

    工作中难免会遇到各种各样的 bug,对于开发环境 or 测试环境的问题还好解决,可以使用 gdb 打断点或者在代码中埋点来定位异常; 但是遇到线上的 bug 就很难受了,由于生产环境不能随意替换.中断 ...

  9. 深入浅出Spring(二)

    IoC概念 控制反转(Inversion of Control)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题. 它还有一个名字叫做依赖注入(Dependency Injection).Io ...

  10. OSI-传输层

    OSI-传输层 端口号(2字节 SYN(1bit) ACK(1bit) 会话多路复用(为什么一个IP地址可以做很多事情?) 源端口地址可以不同 五元组(世界上没有相同的2个五元组) 源IP地址-目的I ...