.NET的配置支持多样化的数据源,我们可以采用内存的变量、环境变量、命令行参数、以及各种格式的配置文件作为配置的数据来源。在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源的配置数据构建为一个统一的配置对象,并以相同的方式读取具体配置节的内容。(本篇提供的实例已经汇总到《ASP.NET Core 6框架揭秘-实例演示版》)

[501]以键值对形式读取配置(源代码

[502]读取结构化配置(源代码

[503]将结构化配置绑定为对象(源代码

[504]将配置定义在JSON文件中(源代码

[505]根据环境动态加载配置文件(源代码

[506]配置内容的实时同步(源代码

[501]以键值对形式读取配置

“原子”配置项体现为一个键值对形式,并且键和值通常都是字符串。假设我们需要通过配置来设定日期/时间的显示格式,我们为此定义了如下这个DateTimeFormatOptions类型,它的四个属性体现了针对DateTime类型的四种显示格式(分别为长日期/时间和短日期/时间)。

public class DateTimeFormatOptions
{
...
public string LongDatePattern { get; set; }
public string LongTimePattern { get; set; }
public string ShortDatePattern { get; set; }
public string ShortTimePattern { get; set; }
}

我们为该类型定义了一个参数类型为IConfiguration接口的构造函数,IConfiguration对象提供的索引使我们可以采用键值对的形式读取每个配置节的值,下面的代码正是以索引的方式得到对应配置并对DateTimeFormatOptions对象的四个属性赋值。

public class DateTimeFormatOptions
{
...
public DateTimeFormatOptions (IConfiguration config)
{
LongDatePattern = config["LongDatePattern"];
LongTimePattern = config["LongTimePattern"];
ShortDatePattern = config["ShortDatePattern"];
ShortTimePattern = config["ShortTimePattern"];
}
}

正如前面所述,IConfiguration对象是由IConfigurationBuilder对象构建的,而原始的配置信息则是通过相应的IConfigurationSource对象提供的,所以创建一个IConfiguration对象的正确编程方式如下:创建一个ConfigurationBuilder(IConfigurationBuilder接口的默认实现类型)对象,并为之注册一个或者多个IConfigurationSource对象,最后利用它来创建我们需要的IConfiguration对象。简单起见,我们采用的IConfigurationSource实现类型为MemoryConfigurationSource,它直接利用一个保存在内存中的字典对象作为最初的配置来源。

using App;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Memory; var source = new Dictionary<string, string>
{
["longDatePattern"] = "dddd, MMMM d, yyyy",
["longTimePattern"] = "h:mm:ss tt",
["shortDatePattern"] = "M/d/yyyy",
["shortTimePattern"] = "h:mm tt"
}; var config = new ConfigurationBuilder()
.Add(new MemoryConfigurationSource { InitialData = source })
.Build(); var options = new DateTimeFormatOptions(config);
Console.WriteLine($"LongDatePattern: {options.LongDatePattern}");
Console.WriteLine($"LongTimePattern: {options.LongTimePattern}");
Console.WriteLine($"ShortDatePattern: {options.ShortDatePattern}");
Console.WriteLine($"ShortTimePattern: {options.ShortTimePattern}");

如上面的代码片段所示,我们创建了一个ConfigurationBuilder对象,并在它上面注册了一个基于内存字典的MemoryConfigurationSource对象。我们接下来利用ConfigurationBuilder对象的Build方法构建IConfiguration对象来创建DateTimeFormatOptions对象。为了验证该Options对象是否与原始配置数据一致,我们将它的四个属性打印在控制台上。程序运行之后,控制台上的输出结果如图1所示)。


图1 以键值对的形式读取配置

[502]读取结构化配置

配置大都具有结构化的层次结构,所以IConfiguration对象同样具有这样的结构。我们姑且将保持树形层次化结构的配置称为“配置树”,一个IConfiguration对象正好是对这棵配置树的某个节点的描述,而整棵配置树则可以由根节点对应的IConfiguration对象来表示。下面以实例来演示如何定义并读取具有层次结构的配置数据。我们依然沿用上一个实例的应用场景,但现在不仅需要设置日期/时间的格式,还需要设置其他数据类型的格式,如表示货币的Decimal类型。因此我们定义了一个CurrencyDecimalFormatOptions类,它的Digits和Symbol属性分别表示小数位数与货币符号,CurrencyDecimalFormatOptions对象依然是利用IConfiguration对象创建的。

public class CurrencyDecimalFormatOptions
{
public int Digits { get; set; }
public string Symbol { get; set; } public CurrencyDecimalFormatOptions (IConfiguration config)
{
Digits = int.Parse(config["Digits"]);
Symbol = config["Symbol"];
}
}

我们定义了如下的FormatOptions类型将两种配置整合在一起,它的DateTime和CurrencyDecimal属性分别表示针对日期/时间与货币数字的格式设置。FormatOptions依然具有一个参数类型为IConfiguration的构造函数,它的两个属性均在此构造函数中被初始化。值得注意的是,初始化这两个属性采用的是调用这个IConfiguration对象的GetSection方法提取的“子配置节”。

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"));
}
}

FormatOptions类型体现的配置具有图2所示的树形层次结构。在前面演示的实例中,我们使用MemoryConfigurationSource对象来提供原始的配置信息,承载原始配置信息的是一个元素类型为KeyValuePair<string, string>的集合,但是它在物理存储上并不具有树形层次结构,那么它如何提供一个结构化的IConfiguration对象承载的数据?

图2 树形层次结构的配置

对于一棵完整的配置树,具体的配置信息存储叶子节点上,所以MemoryConfigurationSource对象只需要在配置字典中保存叶子节点的数据即可。为了描述配置树的结构,配置字典还需要将对应叶子节点在配置树中的路径作为Key。所以MemoryConfigurationSource可以采用表1列举的配置字典对配置树进行扁平化处理。

表1 配置的物理结构

Key

Value

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

$

下面的演示程序按照表1列举的结构创建了一个Dictionary<string, string>对象,并将其作为参数调用IConfigurationBuilder接口的AddInMemoryCollection扩展方法,该方法会根据提供的字段对象创建对应的了MemoryConfigurationSource对象并进行注册。在得到IConfiguration对象之后,我们调用其GetSection方法提取出“Format”配置节,并利用它将FormatOptions对象创建出来。

using App;
using Microsoft.Extensions.Configuration; 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 configuration = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build(); var options = new FormatOptions(configuration.GetSection("Format"));
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}");

在得到利用读取的配置创建的 FormatOptions对象之后,为了验证该对象与原始配置数据是否一致,我们依然将它的相关属性打印在控制台上。这个程序运行之后在控制台上呈现的输出结果如图3所示。


图3 读取结构化的配置

[503]将结构化配置绑定为对象

在前面的实例中,为了创建三个Options对象,我们不得不以键值对的方式从IConfiguration对象中读取每个配置节的值,如果定义的配置项太多,逐条读取配置项其实是一项非常烦琐的工作。如果承载配置数据的IConfiguration对象与对应的Options类型具有兼容的结构,那么利用配置的自动绑定机制可以将IConfiguration对象直接转换成对应的Options对象。配置绑定相应的API定义在“Microsoft.Extensions.Configuration.Binder”这个NuGet包中,

在添加了上述这个NuGet包引用之后,我们删除了三个Options类型的构造函数,然后将演示程序改写成如下的形式。如代码片段所示,在构建出IConfiguration对象之后,我们其调用GetSection方法提取出“Format”配置节,最终的FormatOptions对象直接调用该配置节的Get<T>方法生成出来。修改后的程序运行之后,同样会得到图5-4所示的输出结果。

...
var options = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build()
.GetSection("Format")
.Get<FormatOptions>();
...

[504]将配置定义在JSON文件中

前面演示的三个实例都是采用MemoryConfigurationSource类型的配置源,我们下来演示JSON配置文件的使用。我们在项目根目录下创建一个名为“appsettings.json”的配置文件,并在其中定义了如下的配置。我们将该文件的“Copy to Output Directory”属性设置为“Copy always”(如果项目采用的SDK类型为“Microsoft .NET.Sdk”,该应用在Visual Studio中运行时会将编译输出目录作为当前目录。如果项目采用的SDK类型为 “Microsoft .NET.Sdk.Web”,那么项目根目录就是当前执行的目录,此时不需要设置配置文件的 “Copy to Output Directory” 属性。),其目的是为了让该文件在编译的时候自动复制到输出目录。

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

基于JSON文件的配置源通过JsonConfigurationSource类型来表示。JsonConfigurationSource类型定义在“Microsoft.Extensions.Configuration.Json”这个NuGet包中,所以我们需要为演示程序添加该包的引用。我们不需要手动创建这个JsonConfigurationSource对象,只需要按照如下的方式调用IConfigurationBuilder接口的AddJsonFile扩展方法添加指定的JSON文件即可。执行修改后的程序,我们依然可以得到图3所示的输出结果。

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

[505]根据环境动态加载配置文件

配置内容往往取决于应用当前执行的环境,不同的执行环境(开发、测试、预发和产品等)会采用不同的配置。如果采用基于物理文件的配置,我们可以为不同的环境提供对应的配置文件,具体的做法如下:除了提供一个基础配置文件(如appsettings.json),我们还需要为相应的环境提供对应的差异化配置文件,后者通常采用环境名称作为文件扩展名(如appsettings.production.json)。以目前演示的程序为例,现有的配置文件appsettings.json可以作为基础配置文件,如果某个环境需要采用不同的配置,需要将差异化的配置定义在环境对应的文件中。如图4所示,我们额外添加了两个配置文件(appsettings.staging.json和appsettings.production.json),从文件命名可以看出这两个配置文件分别对应预发环境和产品环境。


图4 针对执行环境的配置文件

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

appsettings.staging.json:

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

appsettings.production.json:

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

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

using App;
using Microsoft.Extensions.Configuration; 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方法将两个配置文件加载进来,两个文件合并后的内容将用于构建最终的IConfiguration对象。我们以命令行的形式启动这个控制台程序,并通过命令行参数指定相应的环境名称。从图5所示的输出结果可以看出,打印出的配置数据(货币的小数位数)确实来源于环境对应的配置文件。


图5 输出与当前环境匹配的配置

[506]配置内容的实时同步

.NET的配置模型提供了针对配置源的监控功能,它能保证一旦原始配置改变之后应用程序能够及时接收到通知,此时我们可以利用预先注册的回调进行配置的同步。前面演示的应用程序采用JSON文件作为配置源,我们希望应用程序能够感知该文件的改变,并在发生改变的时候将新的配置应用到程序之中。为了演示配置的同步,我们对程序做了如下改变。

using App;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives; 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();

如上面的代码片段所示,我们在调用IConfigurationBuilder接口的AddJsonFile扩展方法时将reloadOnChange参数设置为True,进而开启在文件更新的时候自动重新加载的功能。在IConfiguration对象成功构建之后,我们调用它的GetReloadToken方法并利用返回的IChangeToken对象来感知配置源的变化的。一旦配置源发生变化,IConfiguration对象将自动加载新的内容并“自我刷新”。上述程序会在感知到配置源发生变化后自动将新的配置内容打印出来。图6中的输出结果是两次修改货币小数位数导致的。


图6 配置文件更新触发配置的重新加载

ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式

    依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...

  2. ASP.NET Core 6框架揭秘实例演示[07]:文件系统

    ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...

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

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

  4. ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式

    在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...

  5. ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法

    一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...

  6. ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]

    <诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...

  7. ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法

    为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...

  8. ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出

    针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...

  9. ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用

    .NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...

随机推荐

  1. mysql之突破secure_file_priv写webshell

    在某些情况下,当我们进入了一个网站的phpMyAdmin时,想通过select into outfile来写shell,但是通常都会报错. 这是因为在mysql 5.6.34版本以后 secure_f ...

  2. 输出2到n之间的全部素数

    本题要求输出2到n之间的全部素数,每行输出10个.素数就是只能被1和自身整除的正整数.注意:1不是素数,2是素数. 输入格式: 输入在一行中给出一个长整型范围内的整数. 输出格式: 输出素数,每个数占 ...

  3. day 11 算法的时间空间复杂度

    (1).有以下程序: 求输入的n值(除1和n)之外的所有因子之和. 分析:这里函数内的循环体i初值不能为零.%是表示"取余",0除以任何数都不会存在余数的,所有是余数为0. (2) ...

  4. 【刷题-PAT】A1112 Stucked Keyboard (20 分)

    1112 Stucked Keyboard (20 分) On a broken keyboard, some of the keys are always stucked. So when you ...

  5. 雷柏鼠标vt350Q配对

    vt350q 闲鱼捡了个垃圾vt350q,23元,无接收器,不知道好坏 鼠标线 拿到手插上线没法用,后来用了罗技anywhere2s的线可以,原来usb鼠标线是五根. 鼠标毛病 使用后发现滚轮有时候乱 ...

  6. MyCms 开源自媒体系统后台 角色管理&管理员管理操作说明

    角色管理 功能说明 一.添加角色基本信息 填写角色名称.简要描述(一般以角色功能.职位等信息来命名) 二.角色授权 点击右侧的"授权"按钮,进行对角色授权,选择需要授权的功能模块后 ...

  7. 首页页面(html+css+js)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. python for循环while循环数据类型内置方法

    while 条件: 条件成立之后循环执行的子代码块 每次执行完循环体子代码之后都会重新判断条件是否成立 如果成立则继续执行子代码如果不成立则退出 break用于结束本层循环 ### 一:continu ...

  9. 昔日埋雷不经意,今朝踩雷排查难:JetBrains系列IDE使用SFTP连接远程服务器报“EOF while reading packet”解决方法

    写在前面 这是一篇问题解决记录.希望能帮到遇到同样问题的读者. 强烈建议:请您先看解决步骤一节,如果您发现在下的问题和您的问题不一样,就可以及时离开本文,避免浪费时间. 正文 问题描述 在使用GoLa ...

  10. th 表达式的简单使用。

    一.使用场景,页面中,循环遍历,获得控制器穿过来的值. 1.1 控制器 /** * 获得所有的图书信息 * @return */ @RequestMapping("/turnIndexPag ...