[接上篇]提到“配置”二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义在这两个XML格式的文件之中。到了.NET Core的时代,很多我们习以为常的东西都发生了改变,其中就包括定义配置的方式。总的来说,新的配置系统显得更加轻量级,并且具有更好的扩展性,其最大的特点就是支持多样化的数据源。我们可以采用内存的变量作为配置的数据源,也可以将配置定义在持久化的文件甚至数据库中。在对配置系统进行系统介绍之前,我们先从编程的角度来体验一下全新的配置读取方式。

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

在真正的项目开发过程中,我们倾向于像我们演示的实例一样将一组相关的配置转换成一个POCO对象,比如演示实例中的DateTimeFormatOptions、CurrencyDecimalOptions和FormatOptions对象。在前面演示的实例中,为了创建这些封装配置的对象,我们都是采用手工读取配置的形式。如果定义的配置项太多的话,逐条读取配置项其实是一项非常繁琐的工作。

如果承载配置数据的IConfiguration对象与对应的POCO类型具有兼容的结构,我们利用配置的自动绑定机制可以将IConfiguration对象直接转换成对应的POCO对象。对于我们演示的这个实例来说,如果采用自动化配置绑定来创建对应的Options对象,那么这些类型中实现手工绑定的构造函数就不再需要了。

在删除所有Options类型的构造函数之后,我们修改Options对象的创建方式。如下面的代码片段所示,在调用IConfigurationBuilder的Build方法创建出对应IConfiguration对象之后,我们调用GetSection方法得到其“format”配置节,而FormatOptions对象不用再通过调用构造函数来创建,而是直接调用该配置节的Get<T>方法,该方法完成了从IConfiguration到POCO对象之间的自动化绑定。

public class Program
{
public static void Main()
{
var source = new Dictionary<string, string>
{
["format:dateTime:longDatePattern"] = "dddd, MMMM d, yyyy",
["format:dateTime:longTimePattern"] = "h:mm:ss tt",
["format:dateTime:shortDatePattern"] = "M/d/yyyy",
["format:dateTime:shortTimePattern"] = "h:mm tt", ["format:currencyDecimal:digits"] = "2",
["format:currencyDecimal:symbol"] = "$",
}; var options = new ConfigurationBuilder()
.Add(new MemoryConfigurationSource { InitialData = source })
.Build()
.GetSection("format")
.Get<FormatOptions>();
var dateTime = options.DateTime;
var currencyDecimal = options.CurrencyDecimal; Console.WriteLine("DateTime:");
Console.WriteLine($"\tLongDatePattern: {dateTime.LongDatePattern}");
Console.WriteLine($"\tLongTimePattern: {dateTime.LongTimePattern}");
Console.WriteLine($"\tShortDatePattern: {dateTime.ShortDatePattern}");
Console.WriteLine($"\tShortTimePattern: {dateTime.ShortTimePattern}"); Console.WriteLine("CurrencyDecimal:");
Console.WriteLine($"\tDigits:{currencyDecimal.Digits}");
Console.WriteLine($"\tSymbol:{currencyDecimal.Symbol}");
}
}

修改后的程序运行之后,我们同样会得到如下图所示的输出结果。

五、将配置定义在文件中

前面演示的三个实例都是采用 MemoryConfigurationSource将一个字典对象作为配置源,接下来我们演示一种更加常见的配置定义方法,那就是将原始配置的内容定义在一个JSON文件中。我们将原本通过一个内存字典对象承载的配置定义在一个JSON文件中,为此我们在项目的根目录下创建一个名为“appsettings.json”的配置文件,并将该文件的“Copy to Output Directory”属性设置为“Copy always”,其目的是促使项目在编译的时候能够将此文件拷贝到输出目录下。我们采用如下的形式定义关于日期/时间和货币的格式配置。

{
"format": {
"dateTime": {
"longDatePattern" : "dddd, MMMM d, yyyy",
"longTimePattern" : "h:mm:ss tt",
"shortDatePattern" : "M/d/yyyy",
"shortTimePattern": "h:mm tt"
},
"currencyDecimal": {
"digits": 2,
"symbol": "$"
}
}
}

由于配置源发生了改变,原来的MemoryConfigurationSource需要替换成JsonConfigurationSource,不过我们不需要手工创建这个JsonConfigurationSource对象,只需要调用IConfigurationBuilder接口的扩展方法AddJsonFile添加指定的JSON文件即可。执行修改后的程序,我们依然会得到如上图所示的输出结果。

public class Program
{
public static void Main()
{
var options = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build()
.GetSection("format")
.Get<FormatOptions>();
var dateTime = options.DateTime;
var currencyDecimal = options.CurrencyDecimal; Console.WriteLine("DateTime:");
Console.WriteLine($"\tLongDatePattern: {dateTime.LongDatePattern}");
Console.WriteLine($"\tLongTimePattern: {dateTime.LongTimePattern}");
Console.WriteLine($"\tShortDatePattern: {dateTime.ShortDatePattern}");
Console.WriteLine($"\tShortTimePattern: {dateTime.ShortTimePattern}"); Console.WriteLine("CurrencyDecimal:");
Console.WriteLine($"\tDigits:{currencyDecimal.Digits}");
Console.WriteLine($"\tSymbol:{currencyDecimal.Symbol}");
}
}

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

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

以我们目前演示的这个程序为例,现有的这个配置文件appsettings.json可以作为基础配置文件,如果某个环境需要采用不同的配置,我们可以将差异化的配置定义在对应的文件中。如下图所示,我们额外添加了两个配置文件(appsettings.staging.json和appsettings.production.json),从文件命名我们不难看出它们分别对应的是预发和产品环境。

我们在JSON文件中定义了针对日期/时间和货币格式的配置,假设预发环境和产品环境需要采用不同的货币格式,那么我们需要将差异化的配置定义在针对环境的两个配置文件中就可以了。简单起见,我们仅仅将货币的小数位数定义在配置文件中。如下面的代码片段所示,货币小数位数(默认值为2)在预发和产品环境分别被设置为3和4。

appsettings.staging.json:

{
"format": {
"currencyDecimal": {
"digits": 3
}
}
}

appsettings.production.json:

{
"format": {
"currencyDecimal": {
"digits": 4
}
}
}

一般来说,我们会采用环境变量来决定应用的执行环境,但是为了在演示过程中能够灵活地进行环境切换,我们采用命令行参数(比如“/env staging”)的形式来设置环境。到目前为止,针对某一环境的配置被分布到两个配置文件中,那么我们在启动文件的时候就应该根据当前执行环境动态地加载对应的配置文件。如果两个文件涉及到同一段配置,应该首选当前环境对应的那个配置文件。由于配置默认采用“后来居上”的原则,所以应该先加载基础配置文件,再加载针对环境的配置文件。针对执行环境的判断以及针对环境的配置加载体现在如下所示的代码片段中。

class Program
{
static void Main(string[] args)
{
var index = Array.IndexOf(args, "/env");
var environment = index > -1
? args[index + 1]
: "Development"; var options = new ConfigurationBuilder()
.AddJsonFile("appsettings.json",false)
.AddJsonFile($"appsettings.{environment}.json",true)
.Build()
.GetSection("format")
.Get<FormatOptions>();
...
}
}

如上面的代码片段所示,在利用传入的命令行参数确定了当前执行环境之后,我们先后两次调用了IConfigurationBuilder对象的AddJsonFile方法将两个配置文件加载进来,那么两个文件合并后的内容将用于构建Build方法创建的IConfiguration对象。接下来我们以命令行的形式启动这个控制台程序,并通过命令行参数指定相应的环境名称。从如图6-6所示的输出结果可以看出打印出来的配置数据(货币的小数位数)确实来源于环境对应的配置文件。(S605)

七、配置文件的同步

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

我们演示的应用程序采用JSON文件作为配置源,所以我们希望应用程序能够感知到该文件的改变,并在文件发生改变的时候自动加载新的配置比将其重新应用到程序之中。为了演示配置的同步,我们对程序做了如下的改变。

class Program
{
static void Main()
{
var config = new ConfigurationBuilder()
.AddJsonFile(path: "appsettings.json",optional:true,reloadOnChange: true)
.Build();
ChangeToken.OnChange(() => config.GetReloadToken(), () =>
{
var options = config.GetSection("format").Get<FormatOptions>();
var dateTime = options.DateTime;
var currencyDecimal = options.CurrencyDecimal; Console.WriteLine("DateTime:");
Console.WriteLine($"\tLongDatePattern: {dateTime.LongDatePattern}");
Console.WriteLine($"\tLongTimePattern: {dateTime.LongTimePattern}");
Console.WriteLine($"\tShortDatePattern: {dateTime.ShortDatePattern}");
Console.WriteLine($"\tShortTimePattern: {dateTime.ShortTimePattern}"); Console.WriteLine("CurrencyDecimal:");
Console.WriteLine($"\tDigits:{currencyDecimal.Digits}");
Console.WriteLine($"\tSymbol:{currencyDecimal.Symbol}\n\n");
});
Console.Read();
}
}

表示JSON文件配置源的JsonConfigurationSource在默认的情况下并不会监控源文件的变化,所以我们需要在调用IConfigurationBuilder的扩展方法AddJsonFile的时候,通过传入的reloadOnChange参数开启这个功能。通过IConfigurationBuilder的Build方法创建的IConfiguration对象具有一个返回类型为IChangeToken的GetReloadToken方法,我们正是利用它返回的IChangeToken来感知配置源的变化。一旦配置源发生变化,IConfiguration对象将自动加载新的内容,所以我们只需要通过注册的回调将同一个IConfiguration对象应用到程序之中就可以。

我们的程序会在感知到配置源变化后自动将新的配置内容打印出来,所以当该程序被启动之后,我们对appsettings.json文件所做的任何修改都会触发应用对该文件的重新加载。下图所示的输出是我们两次修改货币小数位数导致的。

[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框架揭秘] 配置[2]:读取配置数据[下篇]的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[19]:数据加解密与哈希

    数据保护(Data Protection)框架旨在解决数据在传输与持久化存储过程中的一致性(Integrity)和机密性(confidentiality)问题,前者用于检验接收到的数据是否经过篡改,后 ...

  2. [ASP.NET Core 3框架揭秘] Options[1]: 配置选项的正确使用方式[上篇]

    依赖注入不仅是支撑整个ASP.NET Core框架的基石,也是开发ASP.NET Core应用采用的基本编程模式,所以依赖注入十分重要.依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式 ...

  3. [ASP.NET Core 3框架揭秘] Options[2]: 配置选项的正确使用方式[下篇]

    四.直接初始化Options对象 前面演示的几个实例具有一个共同的特征,即都采用配置系统来提供绑定Options对象的原始数据,实际上,Options框架具有一个完全独立的模型,可以称为Options ...

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

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

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

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

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

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

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

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

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

    .NET Core采用的这个全新的配置模型的一个主要的特点就是对多种不同配置源的支持.我们可以将内存变量.命令行参数.环境变量和物理文件作为原始配置数据的来源.如果采用物理文件作为配置源,我们可以选择 ...

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

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

随机推荐

  1. linux文本编辑器教学

    linux常见服务 一. 文本编辑器 vi vim是vi增强版 vim需要安装 sudo apt-get -y install vim 1 vim的三种工作模式 1 编辑模式 命令模式=>编辑模 ...

  2. Net Framework,Net Core 和 Net Standard 区别

    前几天我在一个群里看到有关这方面的讨论,最后感觉讨论的不是很清晰,有幸的是我们的项目去年就开始迁移NetCore的调研了,我个人多多少少也是有过这方面的研究.下面我将说一下我自己对着三个的认识如果有不 ...

  3. Linux CentOS7部署ASP.NET Core应用程序,并配置Nginx反向代理服务器

    前言: 本篇文章主要讲解的是如何在Linux CentOS7操作系统搭建.NET Core运行环境并发布ASP.NET Core应用程序,以及配置Nginx反向代理服务器.因为公司的项目一直都是托管在 ...

  4. 第二十五章 system v消息队列(一)

    IPC对象的持续性 随进程持续 :一直存在直到打开的最后一个进程结束.(如pipe和FIFO) 随内核持续 :一直存在直到内核自举(内核自举就是把主引导记录加载到内存,并跳转执行这段内存)或显示删除( ...

  5. 第四十章 POSIX条件变量

    条件变量 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中.这种情况就需要用到条件 ...

  6. Spring Boot实战之定制URL匹配规则

    本文首发于个人网站:Spring Boot实战之定制URL匹配规则 构建web应用程序时,并不是所有的URL请求都遵循默认的规则.有时,我们希望RESTful URL匹配的时候包含定界符". ...

  7. CSPS模拟测试59

    这场考得我心态爆炸......... 开场T1只会$n^{2}$,然后发现bfs时每个点只需要被更新一次,其他的更新都是没用的. 也就是说,我们可以只更新还没被更新的点? 于是我先YY了一个链表,发现 ...

  8. 「NOIP模拟赛」数位和乘积(dp,高精)

    统计方案数,要么组合数,要么递推(dp)了. 这是有模拟赛历史以来爆炸最狠的一次 T1写了正解,也想到开long long,但是开错了地方然后数组开大了结果100->0 T3看错题本来简单模拟又 ...

  9. printf的实现原理

    printf的声明    int _cdecl printf(const char* format, …);    _cdecl是C和C++程序的缺省调用方式 _CDEDL调用约定:    1.参数从 ...

  10. Unity 简记(2)--2D移动

    目录 1.输入 1.1直接检测按下哪个按键 1.2.检测水平输入和垂直输入 2.移动 2.1.Transform组件 2.2.RigidBody组件 2.3.NavMeshAgent组件 2.4.Ch ...