读《ASP.NET Core3框架揭秘》之6~配置选项

6.1 读取配置信息

.NET时代,我们用的最多的配置就是app.config和web.config,多年以来我们已经习惯了将结构化的配置定义在这两个XML格式的文件之中。

到了.NET Core的时代,定义配置的方式发生了改变,新的配置系统显得更加轻量级,并且具有更好的扩展性,其最大的特点就是支持多样化的数据源。我们可以采用内存的变量作为配置的数据源,也可以将配置定义在持久化的文件甚至数据库中。

一、配置编程模型三要素

就编程层面来讲,.NET Core的配置系统由如下图所示的三个核心对象构成。读取出来的配置信息最终会转换成一个IConfiguration对象供应用程序使用。IConfigurationBuilder是IConfiguration对象的构建者,而IConfigurationSource则代表配置数据最原始的来源。

6-1

我们根据配置的定义方式(数据源)创建相应的IConfigurationSource对象,并将其注册到IConfigurationBuilder对象上。提供配置的最初来源可能不止一个,我们可以注册多个相同或者不同类型的IConfigurationSource对象到同一个IConfigurationBuilder对象上。IConfigurationBuilder对象正是利用注册的这些IConfigurationSource对象提供的数据构建出我们在程序中使用的IConfiguration对象。

二、以键值对的形式读取配置

虽然大部分情况下的配置从整体来说都具有结构化层次关系,但是“原子”配置项都以体现为最简单的“键值对”形式,并且键和值通常都是字符串。接下来我们会通过一个简单的实例来演示如何以键值对的形式来读取配置。

示例:读取配置

三、 读取结构化的配置

我们定义了另一个名为FormatOptions的类型来表示针对不同数据类型的格式设置。如下面的代码片段所示,它的两个属性DateTime和CurrencyDecimal分别表示针对日期/时间和货币数字的格式设置。FormatOptions依然具有一个参数类型为IConfiguration的构造函数,它的两个属性均在此构造函数中被初始化。值得注意的是初始化这两个属性采用的是当前IConfiguration的“子配置节”,我们通过调用GetSection方法根据指定的名称(“DateTime”和“CurrencyDecimal”)获得这两个子配置节。

public class FormatOptions
{
    public DateTimeFormatOptions DateTime { get; set; }
    public CurrencyDecimalFormatOptions CurrencyDecimal { get; set; }

    public FormatOptions (IConfiguration config)
    {
        DateTime = new DateTimeFormatOptions ( config.GetSection("DateTime"));
        CurrencyDecimal = new CurrencyDecimalFormatOptions (config.GetSection("CurrencyDecimal"));
    }
}

四、 将结构化配置直接绑定为对象

如果承载配置数据的IConfiguration对象与对应的POCO类型具有兼容的结构,我们利用配置的自动绑定机制可以将IConfiguration对象直接转换成对应的POCO对象。

POCO就是简单CLR对象(Plain Old CLR Object)

五、将配置定义在文件中

var options = new ConfigurationBuilder()
         .AddJsonFile("appsettings.json")
         .Build()
         .GetSection("format")
         .Get<FormatOptions>();    

六、根据环境动态加载配置文件

真实项目开发过程中使用的配置往往决定于应用当前执行的环境,也就是说不同的执行环境(开发、测试、预发和产品等)会采用不同的配置。如果采用基于物理文件的配置,我们可以为不同的环境提供对应的配置文件,具体的做法是:除了提供一个“基础配置文件”(比如“appsettings.json”)之外,我们还需为相应的环境提供对应的“差异化”配置文件,后者通常采用环境名称作为文件扩展名(比如“appsettings.production.json”)。

七、配置文件的同步

很多情况下应用程序的配置只会在启动的时候从相应的配置源中读取,并在整个应用的生命周期中保持不变,一旦我们需要重修更新配置,我们不得不重新启动应用程序。.NET Core的配置模型提供了针对配置源的监控功能,它能保证一旦原始的配置改变之后应用程序能够及时接收到通知,此时我们可以利用预先注册的回调进行配置的同步。

6.2 配置模型总体设计

配置的编程模型涉及到三个核心对象,分别通过三个对应的接口(IConfiguration、IConfigurationSource和IConfigurationBuilder)来表示。如果从设计层面来审视背后的配置模型,还缺少另一个名通过IConfigurationProvider接口表示的核心对象。

一、配置数据结构及其转换

相同的数据具有不同的表现形式和承载方式,同时体现出不同的数据结构。对于配置来说,它在被应用程序消费过程中是以IConfiguration对象的形式来体现的,该对象在逻辑上具有一个树形化层次结构,所以将它称之为配置树,并将这棵树视为配置的“逻辑结构”。配置具有多种原始来源,可以是内存对象、物理文件、数据库或者其他自定义的存储介质。如果采用物理文件来存储配置数据,我们还可以选择不同的文件格式,常见的文件类型包括XML、JSON和INI三种,所以配置的原始数据结构是多种多样的。配置模型的最终目的在于提取原始的配置数据并将其转换成一个IConfiguration对象。话句话说,配置模型的使命就在于按照下图所示的方式将配置数据从原始的结构转换成树形层次结构。

6-8

二、IConfiguration

配置在应用程序中总是以一个IConfiguration对象的形式供我们使用。

一个IConfiguration对象表示配置树的某个配置节点。对于组成整棵树的所有配置节点来说,表示根节点的IConfiguration对象与表示其它配置节点的IConfiguration对象是不同的,所以配置模型采用不同的接口来表示它们。根节点所在的IConfiguration对象体现为一个IConfigurationRoot对象,除此之外的其他节点对象则被通过一个IConfigurationSection对象表示,IConfigurationRoot和IConfigurationSection接口都是IConfiguration的继承者。下图为我们展示了由一个IConfigurationRoot对象和一组 IConfigurationSection对象构成的配置树。

6-10

三、IConfigurationProvider

介绍IConfigurationSource对象时,我们说它是对原始配置源的体现。虽然每种不同类型的配置源都具有一个对应的IConfigurationSource实现,但是针对原始数据的读取并不由它来提供,而是委托一个与之对应的IConfigurationProvider对象来完成。

四、IConfigurationSource

IConfiurationSource在配置模型中代表配置源,它被注册到IConfigurationBuilder上为后者创建的IConfiguration提供原始的配置数据。由于针对原始配置数据的读取实现在相应的IConfigurationProvider中,所以IConfigurationSource所起的作用在于提供相应的IConfigurationProvider。如下面的代码片段所示,IConfigurationSource接口具有一个唯一的Build方法根据指定的IConfigurationBuilder对象提供对应的IConfigurationProvider。

public interface IConfigurationSource
{
    IConfigurationProvider Build(IConfigurationBuilder builder);
}

五、IConfigurationBuilder

IConfigurationBulder在整个配置模型中处于一个核心地位,代表原始配置源的IConfigurationSource也注册到它上面,它的作用就在于利用后者提供的原始数据创建出供应用程序使用的IConfiguration对象。

总结:

总的来说,配置模型涉及到四个核心对象,包括承载配置逻辑结构的IConfiguration对象和它的创建者IConfigurationBuilder,以及与配置源相关的IConfigurationSourceIConfigurationProvider。这四个核心对象之间的关系简单而清晰,完全可以通过一句话来概括:IConfigurationBuilder利用注册在它上面的所有IConfigurationSource提供的IConfigurationProvider读取原始配置数据并创建出相应的IConfiguration对象

6.3 将配置绑定为对象

一、IConfigurationBuilder

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

配置绑定可以通过如下几个针对IConfiguration的扩展方法来实现,这些扩展方法都定义在NuGet包“Microsoft.Extensions.Configuration.Binder”中。

var options = new ConfigurationBuilder().AddJsonFile("appsettings.json")
                .Build().GetSection("format").Get<FormatOptions>();

Get和Get<T>方法则直接将指定的IConfiguration对象转换成指定类型的POCO对象。

二、绑定配置项的值

 GetValue方法分别对它们进行类型转换

三、自定义TypeConverter

 如果目标类型支持源自字符串的类型转换,那么我们就能够将配置项的原始值绑定为该类型的对象,而让某个类型支持某种类型转换规则的途径就是为之注册相应的TypeConverter。

四、绑定复合数据类型

 个人基本信息的Profile类,它的三个属性(Gender、Age和ContactInfo)分别表示性别、年龄和联系方式。

 

如果需要通过配置的形式来表示一个完整的Profile对象,我们只需要将四个叶子节点(性别、年龄、电子邮箱地址和电话号码)对应的数据由配置来提供即可。对于承载配置数据的数据字典,我们需要按照如下表所示的方式将这四个叶子节点的路径作为字典元素的Key。

 

Key
Value
Gender Male
Age 18
ContactInfo:Email foobar@outlook.com
ContactInfo:PhoneNo 123456789

五、绑定集合对象

如果配置绑定的目标类型是一个集合(包括数组),那么当前IConfiguration对象的每一个子配置节将绑定为集合的元素。假设我们需要将一个IConfiguration对象绑定为一个元素类型为Profile的集合,它表示的配置树应该具有如下图所示的结构。

6-14

在针对集合类型的配置绑定过程中,如果某个配置节绑定失败,该配置节将被忽略并选择下一个配置节继续进行绑定。但是如果目标类型为数组,最终绑定生成的数组长度与子配置节的个数总是一致的,绑定失败的元素将被设置为Null

var collection = configuration.Get<IEnumerable<Profile>>(); //集合
        Debug.Assert(collection.Count() == 2);

var array = configuration.Get<Profile[]>();  //数组
        Debug.Assert(array.Length == 3);

六、绑定字典

能够通过配置绑定生成的字典是一个实现了IDictionary<string,T>的类型,也就是说配置模型没有对字典的Value类型作任何要求,但是字典对象的Key必须是一个字符串(或者枚举)。如果采用配置树的形式来表示这么一个字典对象,我们会发现它与针对集合的配置树在结构上几乎是一样的。唯一的区别是集合元素的索引直接变成了字典元素的Key。

6-14

也就是说上图所示的这棵配置树同样可以表示成一个具有三个元素的Dictionary<string, Profile>对象 ,它们对应的Key分别是“Foo”、“Bar”和“Baz”,所以我们可以按照如下的方式将承载相同数据的IConfiguration对象绑定为一个IDictionary<string,T>对象。

6.4 配置数据与数据源的实时同步

一、配置数据流

由ConfigurationBuilder(IConfigurationBuilder接口的默认实现)的Build方法提供的IConfiguration对象是一个ConfigurationRoot对象,它代表着整颗配置树,而组成这棵树的配置节则通过ConfigurationSection对象表示。这棵由ConfigurationRoo对象表示的配置树其实是无状态的,也就说不论是ConfigurationRoot对象还是ConfigurationSection对象,它们并没有利用某个字段存储任何的配置数据

ConfigurationRoot对象保持着对所有注册IConfigurationSource提供的IConfigurationProvider对象的引用,当我们调用ConfigurationRoot或者ConfigurationSection相应的API提取配置数据时,最终都会直接从这些IConfigurationProvider中提取数据。换句话说,配置数据在整个模型中只以配置字典的形式存储在IConfigurationProvider对象上面

6-15

应用程序在读取配置时产生的数据流基本体现在上图中。接下来我们从ConfigurationRoot和ConfigurationSection这两个类型的定义来对这个数据流,以及建立在此基础上的配置同步机制作进一步的介绍,不过在这之前我们得先来了解一个名为ConfigurationReloadToken的类型。

二、ConfigurationReloadToken

ConfigurationRoot和ConfigurationSection的GetReloadToken方法返回的IChangeToken对象类型都是ConfigurationReloadToken。

不仅如此,对于组成同一棵配置树的所有节点对应的IConfiguration对象(ConfigurationRoot或者ConfigurationSection)来说,它们的GetReloadToken方法返回的其实是同一个ConfigurationReloadToken对象

还有一点值得强调,IChangeToken其作用不是在配置源发生变化时向应用程序发送通知,它实际上是通知应用程序:配置源已经发生改变,并且新的数据已经被相应的IConfigurationProvider重新加载进来。由于ConfigurationRoot和ConfigurationSection对象都不维护任何数据,它们仅仅将我们的API调用转移到IConfigurationProvider对象上,所以应用程序使用原来的IConfiguration对象就可以获取到最新的配置数据。

ConfigurationReloadToken本质上是对一个CancellationTokenSource对象的封装。从如下的代码片段可以看出,ConfigurationReloadToken与CancellationChangeToken具有类似的定义和实现。

两者唯一不同之处在于:CancellationChangeToken对象利用创建时提供的CancellationTokenSource对象对外发送通知,而ConfigurationReloadToken对象则通过调用OnReload方法利用内置的CancellationTokenSource对象发送通知。

public class ConfigurationReloadToken : IChangeToken
{
    private CancellationTokenSource _cts = new CancellationTokenSource(); 
    public IDisposable RegisterChangeCallback(Action<object> callback, object state) =>_cts.Token.Register(callback, state);    
    public bool ActiveChangeCallbacks => True; 
    public bool HasChanged =>_cts.IsCancellationRequested;

    public void OnReload() => _cts.Cancel();
}

6.5 多样化的配置源

我们可以将内存变量、命令行参数、环境变量和物理文件作为原始配置数据的来源。如果采用物理文件作为配置源,我们可以选择不同的格式(比如XML、JSON和INI等)。如果这些默认支持的配置源形式还不能满足你的需求,我们还可以通过注册自定义IConfigurationSource的方式将其他形式数据作为配置来源。

1、MemoryConfigurationSource

配置源采用一个字典对象(具体来说应该是一个元素类型为KeyValuePair<string, string>的集合)作为存放原始配置数据的容器。

2、EnvironmentVariablesConfigurationSource

顾名思义,环境变量就是描述当前执行环境并影响进程执行行为的变量。

按照作用域的不同,我们将环境变量划分成三类,即分别针对当前系统、当前用户和当前进程的环境变量。对于Windows系统来说,系统和用户级别的环境变量保存在注册表中,其路径分别为“HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment”和“HKEY_CURRENT_USER\Environment ”。 

环境变量的提取和维护可以通过静态类型Environment来完成。

具体来说,我们可以调用它的静态方法GetEnvironmentVariable获得某个指定名称的环境变量的值,而GetEnvironmentVariables方法则会返回所有的环境变量,EnvironmentVariableTarget枚举类型的参数代表环境变量作用域决定的存储位置。如果在调用GetEnvironmentVariable或者GetEnvironmentVariables方法时没有显式指定target参数或者将参数指定为EnvironmentVariableTarget.Process,在进程初始化前存在的所有环境变量(包括针对系统、当前用户和当前进程)将会作为候选列表。

 vs中通过项目属性-调试,设置的环境变量会被保存到launchSettings.json文件中。

 

3、CommandLineConfigurationSource

在以命令行的形式执行某个命令的时候,命令行开关(包括名称和值)体现为一个简单的字符串数组,所以CommandLineConfigurationSource的根本目的在于将命名行开关从字符串数组转换成配置字典。要充分理解这个转换规则,我们先得来了解一下CommandLine
ConfigurationSource支持的命令行开关究竟采用怎样的形式来指定。我们通过一个简单的实例来说明命令行开关的几种指定方式。假设我们有一个命令“exec”并采用如下所示的方式执行某个托管程序(app)。

exec app {options}

4、物理文件

物理文件是我们最常用到的原始配置载体,而最佳的配置文件格式主要有三种,它们分别是JSON、XML和INI,对应的配置源类型分别是JsonConfigurationSource、XmlConfigurationSource和IniConfigurationSource,它们具有如下一个相同的基类FileConfigurationSource。

4.1 FileConfigurationSource

FileConfigurationSource总是利用一个IFileProvider对象来读取配置文件,我们可以利用FileProvider属性来设置这个对象。

配置文件的路径通过Path属性表示,一般来说这是一个针对IFileProvider对象根目录的相对路径

在读取配置文件的时候,这个路径将会作为参数调用IFileProvider对象的GetFileInfo方法得到描述配置文件的IFileInfo对象,IFileInfo对象的CreateReadStream方法最终会被调用来读取文件内容。

public abstract class FileConfigurationSource : IConfigurationSource
{
    public IFileProvider FileProvider { get; set; }
    public string Path { get; set; }
    public bool Optional { get; set; }
    public int ReloadDelay { get; set; }
    public bool ReloadOnChange { get; set; }
    public Action<FileLoadExceptionContext> OnLoadException { get; set; }

    public abstract IConfigurationProvider Build(IConfigurationBuilder builder);
    public void EnsureDefaults(IConfigurationBuilder builder);
    public void ResolveFileProvider();
}

ResolveFileProvider方法

如果FileProvider属性并没有被显式赋值,而我们指定的配置文件路径是一个绝对路径(比如“c:\app\appsettings.json”),那么一个针对配置文件所在目录(“c:\app”)的PhysicalFileProvider将会自动创建出来作为FileProvider的属性值,而Path属性将被设置成配置文件名。如果指定的仅仅是一个相对路径,FileProvider属性将不会被自动初始化。这个逻辑实现在ResolveFileProvider方法中:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
using System;

namespace ConfigurationFileDemo
{
    public class TestFileProvider
    {
        public static void Test1()
        {
            var source = new FakeConfigurationSource
            {
                Path = @"C:\tmp\appsettings.json"
            };
            Console.WriteLine(source.FileProvider == null);

            source.ResolveFileProvider();
            var fileProvider = (PhysicalFileProvider)source.FileProvider;
            Console.WriteLine(fileProvider?.Root);
            Console.WriteLine(source.Path);
        }
    }

    /// <summary>
    /// 伪造的配置源,测试绝对路径
    /// </summary>
    public class FakeConfigurationSource : FileConfigurationSource
    {
        public override IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            throw new NotImplementedException();
        }
    }
}

可缺省配置文件

FileConfigurationSource的Optional表示当前配置源是否可以缺省。如果该属性被设置成False,即使指定的配置文件不存在也不会抛出异常。

可缺省的配置文件在支持多环境的场景中具有广泛的应用。正如前面实例演示的一样,我们可以按照如下的方式加载两个配置文件,基础配置文件appsettings.json一般包含相对全面的配置,针对某个环境的差异化配置则定义在appsettings.{environment}.json文件中。

前者是必需的,后者则是可以缺省的,这保证了应用程序在缺少基于当前环境的差异化配置文件的情况下依然可以使用定义在基础配置文件中的默认配置。

var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile(path: "appsettings.json", optional: false)
    .AddJsonFile(path: $"appsettings.{environment}.json", optional: true)
    .Build();

配置数据的实时同步

FileConfigurationSource借助IFileProvider对象提供的文件系统监控功能实现了配置文件在更新后的自动实时加载功能,这个特性通过ReloadOnChange属性来开启或者关闭。默认情况下这个特性是关闭的,我们需要通过将这个属性设置为True来显式地开启该特性。

如果开启了配置文件的重新加载功能,一旦配置文件发生变化,IFileProvider对象会在第一时间将通知发送给对应的FileConfigurationProvider对象,后者会调用Load方法重新加载配置文件。

考虑到有可能针对配置文件的写入此时尚未结束,FileConfigurationSource采用了 “延时加载” 的方式来解决这个问题,具体的延时通过ReloadDelay属性来控制。该属性的单位是毫秒,默认设置的延时为250毫秒。

4.2 JsonConfigurationSource

JsonConfigurationSource代表针对通过JSON文件的配置源,该类型定义在NuGet包“Microsoft.Extensions.Configuration.Json”中。

从如下给出的定义可以看出,JsonConfigurationSource重写的Build方法在提供对应的JsonConfigurationProvider对象之前会调用EnsureDefaults方法,这个方法确保用于读取配置文件的IFileProvider对象和处理配置文件加载异常的处理器被初始化。

JsonConfigurationProvider对象派生于抽象类FileConfigurationProvider,它利用重写的Load方法读取配置文件的内容并将其转换成配置字典。

public class JsonConfigurationSource : FileConfigurationSource
{
    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        EnsureDefaults(builder);
        return new JsonConfigurationProvider(this);
    }
}

public class JsonConfigurationProvider : FileConfigurationProvider
{
    public JsonConfigurationProvider(JsonConfigurationSource source);
    public override void Load(Stream stream);
}

IConfigurationBuilder接口具有如下几个名为AddJsonFile扩展方法来注册JsonConfigurationSource。

如果调用第一个AddJsonFile方法重载,我们可以利用指定的Action<JsonConfigurationSource>对象对创建的JsonConfigurationSource进行初始化。至于其他AddJsonFile方法重载,实际上就是通过相应的参数初始化JsonConfigurationSource对象的Path、Optional和ReloadOnChange属性罢了。

public static class JsonConfigurationExtensions
{
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource);
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path);
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional);
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange);
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange);
}

当使用JSON文件来定义配置的时候,我们会发现不论对于何种数据结构(复杂对象、集合、数组和字典),我们都能通过JSON格式以一种简单而自然的方式来定义它们。

4.3 XmlConfiguationSource

XML也是一种常用的配置定义形式,它对数据的表达能力甚至强于JSON,几乎所有类型的数据结构都可以通过XML表示出来。

当我们通过一个XML元素表示一个复杂对象的时候,对象的数据成员定义成当前XML元素的子元素。如果数据成员是一个简单数据类型,我们还可以选择将其定义成当前XML元素的属性(Attribute)。

虽然XML对数据结构的表达能力总体要强于JSON,但是作为配置模型的数据来源却有自己的局限性,比如它们对集合的表现形式有点不尽如人意【因为字典的Key必须是唯一的,这必然要求最终构成配置树的每个节点必须具有不同的路径。】

4.4 IniConfigurationSource

“INI”是“Initialization”的缩写,INI文件又被称为初始化文件,它是Windows系统普遍使用的配置文件,同时也被一些Linux和Unix系统所支持。INI文件直接以键值对的形式定义配置项,如下所示的代码片段体现了INI文件的基本格式。总的来说,INI文件以单纯的“{Key}={Value}”的形式定义配置项,{Value}可以定义在可选的双引号中(如果值的前后包括空白字符,必须使用双引号,否则会被忽略)。

[Section]
key1=value1
key2 = " value2 "
; comment
# comment
/ comment

除了以“{Key}={Value}”的形式定义的原子配置项外,我们还可以采用“[{SectionName}]”的形式定义配置节对它们进行分组。中括号(“[]”)作为下一个的配置节开始的标志和上一个配置节结束的标志,所以采用INI文件定义的配置节并不存在层次化的结构,即没有“子配置节”的概念。除此之外,我们可以在INI中定义相应的注释,注释行前置的字符可以采用“;”、“#”或者“/”。

由于INI文件自身就体现为一个数据字典,所以我们可以采用“路径化”的Key来定义最终绑定为复杂对象、集合或者字典的配置数据。如果采用INI文件来定义一个Profile对象的基本信息,我们就可以采用如下的定义形式。

Gender = "Male"
Age  = "18"

[ContactInfo]
EmailAddress = "foobar@outlook.com"
PhoneNo  = "123456789"

5、自定义ConfigurationSource

我们还可以通过自定义IConfigurationSource实现类型来支持我们希望的配置源。就配置数据的持久化方式来说,将配置存储在数据库中应该是一种常见的方式。接下来我们会创建一个针对数据库的IConfigurationSource实现类型,它采用Entity Framework Core来完成数据库的存取操作。

我们将这个自定义ConfigurationSource命名为。在正式介绍的实现之前,我们先来看看它在项目中的应用。我们将配置保存在mysql数据库中的某个数据表中,并采用Entity Framework Core来读取它。

我们将连接字符串作为配置定义在一个名为“appSettings.json”的JSON文件中。

{
  "ConnectionStrings": {
    "DefaultDb": "server=120.79.67.39;uid=root;pwd=;port=3306;database=TestDbX;sslmode=Preferred;"
  }
}

我们首先创建了一个ConfigurationBuilder对象,并在它上面注册了一个指向connectionString.json文件的JsonConfigurationSource对象。

针对DbConfigurationSource对象的注册体现在扩展方法AddDatabase上,这个方法具有两个参数,分别代表连接字符串的名称和初始的配置数据。前者正是connectionString.json设置的连接字符串名称DefaultDb,后者是一个字典对象,它提供的原始配置正好可以构成一个Profile对象。

在利用ConfigurationBuilde对象创建出相应的IConfiguration对象之后,我们读取配置将其绑定为一个Profile对象。

public class Program
{
    static void Main()
    {
        var initialSettings = new Dictionary<string, string>
        {
            ["Gender"] = "Male",
            ["Age"] = "18",
            ["ContactInfo:EmailAddress"] = "foobar@outlook.com",
            ["ContactInfo:PhoneNo"] = "123456789"
        };

        var profile = new ConfigurationBuilder()
            .AddJsonFile("appSettings.json")
            .AddDatabase("DefaultDb", initialSettings)
            .Build()
            .Get<Profile>();

        Debug.Assert(profile.Gender == Gender.Male);
        Debug.Assert(profile.Age == 18);
        Debug.Assert(profile.ContactInfo.EmailAddress == "foobar@outlook.com");
        Debug.Assert(profile.ContactInfo.PhoneNo == "123456789");
    }
}

如上面的代码片断所示,针对DbConfigurationSource的应用仅仅体现在我们为IConfigurationBuilder对象定义的AddDatabase扩展方法上,所以使用起来是非常方便的,那么这个扩展方法背后有着怎样的逻辑实现呢?

DbConfigurationSource采用Entity Framework Core并以Code First的方式进行数据操作。

 

 

posted @ 2020-12-03 20:40  peterYong  阅读(115)  评论(0编辑  收藏  举报