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

针对环境变量的配置源通过如下这个 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", "[email protected]utlook.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, "[email protected]", "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” 的值。

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

原文地址:https://www.cnblogs.com/artech/p/inside-asp-net-core-05-06.html

时间: 2024-08-01 18:04:28

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

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

在<配置模型总体设计>介绍配置模型核心对象的时候,我们刻意回避了与配置同步相关的API,现在我们利用一个独立文章来专门讨论这个话题.配置的同步涉及到两个方面:第一,对原始的配置源实施监控并在其发生变化之后重新加载配置:第二,配置重新加载之后及时通知应用程序进而使应用能够及时使用最新的配置.要了解配置同步机制的实现原理,我们先得了解一下配置数据的流向. 一.配置数据流 通过前面的介绍,我们已经对配置模型有了充分的了解,处于核心地位的 IConfigurationBuilder对象借助注册的ICo

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

原文:[ASP.NET Core 3框架揭秘] 配置[3]:配置模型总体设计 在<读取配置数据>([上篇],[下篇])上面一节中,我们通过实例的方式演示了几种典型的配置读取方式,接下来我们从设计的维度来重写认识配置模型.配置的编程模型涉及到三个核心对象,分别通过三个对应的接口(IConfiguration.IConfigurationSource和IConfigurationBuilder)来表示.如果从设计层面来审视背后的配置模型,还缺少另一个名通过IConfigurationProvide

[ASP.NET Core 3框架揭秘] 配置[9]:自定义配置源

我们在前面对配置模型中默认提供的各种IConfigurationSource实现类型进行了深入详尽的介绍,如果它们依然不能满足项目中的需求,我们还可以通过自定义IConfigurationSource实现类型来支持我们希望的配置源.就配置数据的持久化方式来说,将配置存储在数据库中应该是一种常见的方式.接下来我们会创建一个针对数据库的IConfigurationSource实现类型,它采用Entity Framework Core来完成数据库的存取操作. 我们将这个自定义Configuration

[ASP.NET Core 3框架揭秘] Options[7]: 与配置系统的整合

Options模型本身与配置系统完全没有关系,但是配置在大部分情况下会作为绑定Options对象的数据源,所以有必要将两者结合在一起.与<扩展与定制>演示的两个例子一样,针对配置系统的集成同样是通过定制Options模型相应的对象来实现的.具体来说,集成配置系统需要解决如下两个问题: 将承载配置数据的IConfiguration对象绑定为Options对象. 自动感知配置数据的变化. 第一个问题涉及针对Options对象的初始化问题,这自然是通过自定义IConfigureOptions<

[ASP.NET Core 3框架揭秘] 依赖注入:控制反转

ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样会频繁地使用到它们.对于这里提到的这几个基础框架,依赖注入尤为重要.ASP.NET Core应用在启动以及后续针对请求的处理过程中,它会依赖各种的组件提供服务.为了便于定制,这些组件一般会以接口的形式进行"标准化",我们将这些标准化的组件统一称为"服务(Service)"

[ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务

毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容器不仅为ASP.NET Core框架自身提供必要的服务,同时也是应用程序的服务提供者,依赖注入已经成为了ASP.NET Core应用的基本编程模式. 一.服务的注册与消费 为了让读者朋友们能够更加容易地认识.NET Core提供的依赖注入框架,我在"<一个迷你版DI框架>"中特

[ASP.NET Core 3框架揭秘] Options[4]: Options模型[下篇]

六.IOptionsMonitorCache<TOptions> IOptionsFactory<TOptions>解决了Options的创建与初始化问题,但由于它自身是无状态的,所以Options模型对Options对象实施缓存可以获得更好的性能.Options模型中针对Options对象的缓存由IOptionsMonitorCache<TOptions>对象来完成,如下所示的代码片段是该接口的定义. public interface IOptionsMonitorC

[ASP.NET Core 3框架揭秘] 跨平台开发体验: Windows [上篇]

微软在千禧年推出 .NET战略,并在两年后推出第一个版本的.NET Framework和IDE(Visual Studio.NET 2002,后来改名为Visual Studio),如果你是一个资深的.NET程序员,相信传统的.NET应用的开发方式已经深深地烙印在你的脑子里面..NET Core带来了全新的开发体验,但开发方式的差异根本不足以成为你快速跨入.NET Core 世界的门槛,因为在.NET Core在很多方面比传统的.NET Framework应用开发要简单.为了消除很多尚未接触过.

[ASP.NET Core 3框架揭秘] 跨平台开发体验: Linux

如果想体验Linux环境下开发.NET Core应用,我们有多种选择.一种就是在一台物理机上安装原生的Linux,我们可以根据自身的喜好选择某种Linux Distribution,目前来说像RHEL.Ubuntu.Debian.Fedora.CentOS和SUSE这些主流的Distribution都是支持的.如果读者朋友们觉得这种方式比较麻烦,我们也可以采用虚拟机的形式安装相应的Linux Distribution,比如我经常使用的都是安装在VirtualBox上的Ubuntu.对于X64 W