ASP.NET Core 6框架揭秘实例演示[09]:配置绑定
我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定。除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置节绑定为一个具有对应结构的符合对象。除此之外,配置绑定还支持针对数据、集合和字典类型的绑定。(本篇提供的实例已经汇总到《ASP.NET Core 6框架揭秘-实例演示版》)
[507]绑定配置项的值(源代码)
[508]类型转换器在配置绑定中的应用(源代码)
[509]复合对象的配置绑定(源代码)
[510]集合的配置绑定(源代码)
[511]集合和数组的配置绑定的差异(源代码)
[512]字典的配置绑定(源代码)
[507]绑定配置项的值
最简单配置绑定的莫过于针对配置树叶子节点配置节的绑定。这样的配置节承载着原子配置项的值,而且这个值是一个字符串,所以针对它的配置绑定最终体现为如何将这个字符串转换成指定的目标类型,这样的操作体现在IConfiguration接口如下两个GetValue扩展方法上。
public static class ConfigurationBinder
{
public static T GetValue<T>(IConfiguration configuration, string sectionKey);
public static T GetValue<T>(IConfiguration configuration, string sectionKey, T defaultValue);
public static object GetValue(IConfiguration configuration, Type type, string sectionKey);
public static object GetValue(IConfiguration configuration, Type type, string sectionKey, object defaultValue);
}
对于上面给出的这四个重载的GetValue方法,其中两个方法提供了一个表示默认值的参数defaultValue,如果对应配置节的值为Null或者空字符串,那么指定的默认值将作为方法的返回值。其他两个重载实际上是将Null或者Default(T)作为默认值。这些GetValue方法会将配置节名称(对应参数sectionKey)作为参数调用指定IConfiguration对象的GetSection方法得到表示对应配置节的IConfigurationSection对象,然后将它的Value属性提取出来按照如下规则转换成目标类型。
- 如果目标类型为object,那么直接返回原始值(字符串或者Null)。
- 如果目标类型不是Nullable<T>,那么针对目标类型的TypeConverter将被用来完成类型转换。
- 如果目标类型为Nullable<T>,在原始值不是Null或者空字符串的情况下会直接返回Null,否则会按照上面的规则将值转换成类型基础T。
为了验证上述这些类型转化规则,我们编写了如下测试程序。如代码片段所示,我们利用注册的MemoryConfigurationSource添加了三个配置项,对应的值分别为Null、空字符串和“123”。在将IConfiguration对象构建出来后,我们调用它的GetValue<T>将三个值转换成Object、Int32和Nullable<Int32>类型。
using Microsoft.Extensions.Configuration;
using System.Diagnostics; var source = new Dictionary<string, string?>
{
["foo"] = null,
["bar"] = "",
["baz"] = "123"
}; var root = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build(); //针对object
Debug.Assert(root.GetValue<object>("foo") == null);
Debug.Assert("".Equals(root.GetValue<object>("bar")));
Debug.Assert("123".Equals(root.GetValue<object>("baz"))); //针对普通类型
Debug.Assert(root.GetValue<int>("foo") == 0);
Debug.Assert(root.GetValue<int>("baz") == 123); //针对Nullable<T>
Debug.Assert(root.GetValue<int?>("foo") == null);
Debug.Assert(root.GetValue<int?>("bar") == null);
[508]类型转换器在配置绑定中的应用
按照前面介绍的类型转换规则,如果目标类型支持源自字符串的类型转换,就能够将配置项的原始值绑定为该类型的对象。在下面的代码片段中,我们定义了一个表示二维坐标的Point记录(Record),并且为它注册了一个针对PointTypeConverter的类型转换器。PointTypeConverter通过实现的ConvertFrom方法将坐标的字符串表达式(如“123”和“456”)转换成一个Point对象。
[TypeConverter(typeof(PointTypeConverter))]
public readonly record struct Point(double X, double Y); public class PointTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => sourceType == typeof(string); public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var split = (value.ToString() ?? "0.0,0.0").Split(',');
double x = double.Parse(split[0].Trim().TrimStart('('));
double y = double.Parse(split[1].Trim().TrimEnd(')'));
return new Point(x,y);
}
}
由于定义的Point类型支持源自字符串的类型转换,所以如果配置项的原始值(字符串)具有与之兼容的格式,我们就可以按照如下方式将其绑定为一个Point对象。
using App;
using Microsoft.Extensions.Configuration;
using System.Diagnostics; var source = new Dictionary<string, string>
{
["point"] = "(123,456)"
}; var root = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build(); var point = root.GetValue<Point>("point");
Debug.Assert(point.X == 123);
Debug.Assert(point.Y == 456);
[509]复合对象的配置绑定
这里所谓的复合类型就是一个具有属性数据成员的自定义类型。如果用一棵树表示一个复合对象,那么叶子节点承载所有的数据,并且叶子节点的数据类型均为基元类型。如果用数据字典来提供一个复杂对象所有的原始数据,那么这个字典中只需要包含叶子节点对应的值即可。我们只要将叶子节点所在的路径作为字典元素的Key,就可以通过一个字典对象体现复合对象的结构。
public readonly record struct Profile(Gender Gender, int Age, ContactInfo ContactInfo);
public readonly record struct ContactInfo(string EmailAddress, string PhoneNo);
public enum Gender
{
Male,
Female
}
上面的代码片段定义了一个表示个人基本信息的Profile记录,它的Gender、Age和ContactInfo属性分别表示性别、年龄和联系方式。表示联系方式的ContactInfo记录定义了EmailAddress和PhoneNo属性,分别表示电子邮箱地址和电话号码。一个完整的Profile对象可以通过图1所示的树来体现。

图1 复杂对象的配置树
如果需要通过配置的形式表示一个完整的Profile对象,只需要提供四个叶子节点(性别、年龄、电子邮箱地址和电话号码)对应的配置数据,配置字典只需要按照表1来存储这四个键值对就可以了。
表1 针对复杂对象的配置数据结构
|
Key |
Value |
|
Gender |
Male |
|
Age |
18 |
|
ContactInfo:Email |
foobar@outlook.com |
|
ContactInfo:PhoneNo |
123456789 |
我们通过下面的程序来验证针对复合数据类型的绑定。我们先创建一个ConfigurationBuilder对象,并利用注册的MemoryConfigurationSource对象添加了表5-2所示的配置数据。在构建出IConfiguration对象之后,我们调用它的Get<T>扩展方法将其绑定为Profile对象。
using App;
using Microsoft.Extensions.Configuration;
using System.Diagnostics; var source = new Dictionary<string, string>
{
["gender"] = "Male",
["age"] = "18",
["contactInfo:emailAddress"] = "foobar@outlook.com",
["contactInfo:phoneNo"] = "123456789"
}; var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build(); var profile = configuration.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");
[510]集合的配置绑定
如果配置绑定的目标类型是一个集合(包括数组),那么当前IConfiguration对象的每个子配置节将绑定为集合的元素。如果目标类型为元素类型为Profile的集合,那么配置树应该具有图2所示的结构。既然能够正确地将集合对象通过一个合法的配置树体现出来,那么就可以将它转换成配置字典

图2 集合对象的配置树
我们利用如下的实例来演示针对集合的配置绑定。如代码片段所示,我们创建了一个ConfigurationBuilder对象,并为它注册了一个MemoryConfigurationSource对象,并利用注册的MemoryConfigurationSource对象添加了配置数据。在构建出IConfiguration对象之后,我们调用它的Get<T>扩展方法将它分别绑定为一个IList<Profile>和Profile数组对象。
using App;
using Microsoft.Extensions.Configuration;
using System.Diagnostics; var source = new Dictionary<string, string>
{
["0:gender"] = "Male",
["0:age"] = "18",
["0:contactInfo:emailAddress"] = "foo@outlook.com",
["0:contactInfo:phoneNo"] = "123", ["1:gender"] = "Male",
["1:age"] = "25",
["1:contactInfo:emailAddress"] = "bar@outlook.com",
["1:contactInfo:phoneNo"] = "456", ["2:gender"] = "Female",
["2:age"] = "36",
["2:contactInfo:emailAddress"] = "baz@outlook.com",
["2:contactInfo:phoneNo"] = "789"
}; var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build(); var list = configuration.Get<IList<Profile>>();
Debug.Assert(list[0].ContactInfo.EmailAddress == "foo@outlook.com");
Debug.Assert(list[1].ContactInfo.EmailAddress == "bar@outlook.com");
Debug.Assert(list[2].ContactInfo.EmailAddress == "baz@outlook.com"); var array = configuration.Get<Profile[]>();
Debug.Assert(array[0].ContactInfo.EmailAddress == "foo@outlook.com");
Debug.Assert(array[1].ContactInfo.EmailAddress == "bar@outlook.com");
Debug.Assert(array[2].ContactInfo.EmailAddress == "baz@outlook.com");
[511]集合和数组的配置绑定的差异
针对集合的配置绑定不会因为某个元素的绑定失败而终止。如果目标类型是数组,最终绑定生成的数组长度与子配置节的个数总是一致的。如果目标类型是列表,将不会生成对应的元素。我们将上面演示程序做了稍许的修改,将第一个元素的性别从“Male”改为“男”,那么针对这个Profile元素绑定将会失败。如果将目标类型设置为IEnumerable<Profile>,那么最终生成的集合只有两个元素。倘若目标类型切换成Profile数组,数组的长度依然为3,但是第一个元素是空。
using App;
using Microsoft.Extensions.Configuration;
using System.Diagnostics; var source = new Dictionary<string, string>
{
["0:gender"] = "男",
["0:age"] = "18",
["0:contactInfo:emailAddress"] = "foo@outlook.com",
["0:contactInfo:phoneNo"] = "123", ["1:gender"] = "Male",
["1:age"] = "25",
["1:contactInfo:emailAddress"] = "bar@outlook.com",
["1:contactInfo:phoneNo"] = "456", ["2:gender"] = "Female",
["2:age"] = "36",
["2:contactInfo:emailAddress"] = "baz@outlook.com",
["2:contactInfo:phoneNo"] = "789"
}; var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build(); var list = configuration.Get<IList<Profile>>();
Debug.Assert(list.Count == 2);
Debug.Assert(list[0].ContactInfo.EmailAddress == "bar@outlook.com");
Debug.Assert(list[1].ContactInfo.EmailAddress == "baz@outlook.com"); var array = configuration.Get<Profile[]>();
Debug.Assert(array.Length == 3);
Debug.Assert(array[0] == default);
Debug.Assert(array[1].ContactInfo.EmailAddress == "bar@outlook.com");
Debug.Assert(array[2].ContactInfo.EmailAddress == "baz@outlook.com");
[512]字典的配置绑定
能够通过配置绑定生成的字典是一个实现了IDictionary<string,T>的类型,它Key必须是一个字符串(或者枚举)。如果采用配置树的形式表示这样一个字典对象,就会发现它与针对集合的配置树在结构上几乎是一样的,唯一的区别是集合元素的索引直接变成字典元素的Key。也就是说,图2所示的配置树同样可以表示成一个具有三个元素的Dictionary<string, Profile>对象,它们对应的Key分别“0”、“1”和“2”,所以我们可以按照如下方式将承载相同结构数据的IConfiguration对象绑定为一个IDictionary<string, Profile >对象。如代码片段所示,我们将表示集合索引的整数(“0”、“1”和“2”)改成普通的字符串(“foo”、“bar”和“baz”)。
using App;
using Microsoft.Extensions.Configuration;
using System.Diagnostics; var source = new Dictionary<string, string>
{
["foo:gender"] = "Male",
["foo:age"] = "18",
["foo:contactInfo:emailAddress"] = "foo@outlook.com",
["foo:contactInfo:phoneNo"] = "123", ["bar:gender"] = "Male",
["bar:age"] = "25",
["bar:contactInfo:emailAddress"] = "bar@outlook.com",
["bar:contactInfo:phoneNo"] = "456", ["baz:gender"] = "Female",
["baz:age"] = "36",
["baz:contactInfo:emailAddress"] = "baz@outlook.com",
["baz:contactInfo:phoneNo"] = "789"
}; var profiles = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build()
.Get<IDictionary<string,Profile>>();; Debug.Assert(profiles["foo"].ContactInfo.EmailAddress == "foo@outlook.com");
Debug.Assert(profiles["bar"].ContactInfo.EmailAddress == "bar@outlook.com");
Debug.Assert(profiles["baz"].ContactInfo.EmailAddress == "baz@outlook.com");
ASP.NET Core 6框架揭秘实例演示[09]:配置绑定的更多相关文章
- ASP.NET Core 6框架揭秘实例演示[07]:文件系统
ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...
- ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式
.NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...
- ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式
依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...
- ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式
在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...
- ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法
一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...
- ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]
<诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...
- ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法
为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...
- ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出
针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...
- ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用
.NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...
随机推荐
- T-SQL创建数据库常用方法2020年10月29日20:12:04网课笔记
2.接口的作用 第一.方便框架的设计.利于团队的开发. 第二.方便项目拓展.高内聚.低耦合. 3.反射 [1]反射的理解:通过读取程序集的信息,找到相关的类型和类型的成员,也可以得到相关的对象.而这种 ...
- Git在实际生产中的使用
文章目录 Git在实际生产中的使用 简单情况下的代码提交 Fetch and Pull 仅获取某分支的代码 远程仓库已经合并了别人的代码 冲突产生原因与解决办法 不恰当的多个Commit合并为一个 G ...
- 【白话科普】《逆局》最终 boss 隐藏自己的方式是?
二狗子最近在看一个很火的电视剧<逆局>.作为一部悬疑犯罪剧,剧中多个案件交织并进,悬念和转折拉满,让狗子看的直呼过瘾.特别最后一幕,杨副座和主角团同时对 U 盘中的关键证据"器官 ...
- 🏆【Alibaba中间件技术系列】「Nacos技术专题」配置中心加载原理和配置实时更新原理分析(上)
官方资源 https://nacos.io/zh-cn/docs/quick-start.html Nacos之配置中心 动态配置管理是 Nacos的三大功能之一,通过动态配置服务,可以在所有环境中以 ...
- golang中channel
1. Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication). 2. select package main im ...
- insert插入日期
7.5.insert插入日期 数字格式化:format select ename,sal from emp: 格式化数字:fromat(数字,'格式') select ename,format(sal ...
- 阿里巴巴基于应用和变更的交付模式|阿里巴巴DevOps实践指南
编者按:本文源自阿里云云效团队出品的<阿里巴巴DevOps实践指南>,扫描上方二维码或前往:https://developer.aliyun.com/topic/devops,下载完整版电 ...
- python27day
内容回顾 super 遵循mro算法 只在新式类中能适应 py2新式类中需要自己添加参数 封装 广义上的封装 狭义上的封装 (__名字) 方法名私有化 实例变量私有化 静态变量私有化 私有化的特点 只 ...
- linux 常用命令。
/* Linux常用命令? 1 查看 ls 展示当前目录下的可见文件 ls -a 展示当前目录下所有的文件(包括隐藏的文件) ls -l(ll) ...
- 使用Docker快速搭建Halo个人博客到阿里云服务器上[附加主题和使用域名访问]
一.前言 小编买了一个服务器也是一直想整个网站,一直在摸索,看了能够快速搭建博客系统的教程.总结了有以下几种方式,大家按照自己喜欢的去搭建: halo wordpress hexo vuepress ...