[ASP.NET Core 3框架揭秘] 配置[4]:将配置绑定为对象
虽然应用程序可以直接利用通过IConfigurationBuilder对象创建的IConfiguration对象来提取配置数据,但是我们更倾向于将其转换成一个POCO对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定。配置绑定可以通过如下几个针对IConfiguration的扩展方法来实现,这些扩展方法都定义在NuGet包“Microsoft.Extensions.Configuration.Binder”中。
一、ConfigurationBinder
public static class ConfigurationBinder
{
public static void Bind(this IConfiguration configuration, object instance);
public static void Bind(this IConfiguration configuration, object instance, Action<BinderOptions> configureOptions);
public static void Bind(this IConfiguration configuration, string key, object instance); public static T Get<T>(this IConfiguration configuration);
public static T Get<T>(this IConfiguration configuration, Action<BinderOptions> configureOptions);
public static object Get(this IConfiguration configuration, Type type);
public static object Get(this IConfiguration configuration, Type type, Action<BinderOptions> configureOptions);
} public class BinderOptions
{
public bool BindNonPublicProperties { get; set; }
}
Bind方法将指定的IConfiguration对象(对应于configuration参数)绑定一个预先创建的对象(对应于instance参数),如果参数绑定的只是当前IConfiguration对象的某个子配置节,我们需要通过参数sectionKey指定对应子配置节的相对路径。Get和Get<T>方法则直接将指定的IConfiguration对象转换成指定类型的POCO对象。
旨在生成POCO对象的配置绑定实现在IConfiguration接口的扩展方法Bind上。配置绑定的目标类型可以是一个简单的基元类型,也可以是一个自定义数据类型,还可以是一个数组、集合或者字典类型。通过前面的介绍我们知道IConfigurationProvider对象将原始的配置数据读取出来后会将其转换成Key和Value均为字符串的数据字典,那么针对这些完全不同的目标类型,原始的配置数据如何通过数据字典的形式来体现呢?
二、绑定配置项的值
我们知道配置模型采用字符串键值对的形式来承载基础配置数据,我们将这组键值对称为配置字典,扁平的字典因为采用路径化的Key使配置项在逻辑上具有了层次结构。IConfigurationBuilder对象将配置的层次化结构体现在由它创建的IConfigurationRoot对象上,我们将IConfigurationRoot对象视为一棵配置树。所谓的配置绑定体现为如何将映射为配置树上某个节点的IConfiguration对象(可以是IConfigurationRoot对象或者IConfigurationSection对象)转换成一个对应的POCO对象。
对于针对IConfiguration对象的配置绑定来说,最简单的莫过于针对叶子节点的IConfigurationSection对象的绑定。表示配置树叶子节点的IConfigurationSection对象承载着原子配置项的值,而且这个值是一个字符串,那么针对它的配置绑定最终体现为如何将这个字符串转换成指定的目标类型,这样的操作体现在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);
}
对于给出的这四个重载,其中两个方法定义了一个表示默认值的defaultValue参数,如果对应配置节的值为Null或者空字符串,指定的默认值将作为方法的返回值。对于其他的方法重载,它们实际上将Null或者Default(T)作为隐式默认值。上述这些GetValue方法被执行的时候,它们会将配置节名称(对应sectionKey参数)作为参数调用指定IConfiguation对象的GetSection方法得到表示对应配置节的IConfigurationSection对象,它的Value属性被提取出来并按照如下的逻辑转换成目标类型:
- 如果目标类型为object,直接返回原始值(字符串或者Null)。
- 如果目标类型不是Nullable<T>,那么针对目标类型的TypeConverter将被用来做类型转换。
- 如果目标类型为Nullable<T>,那么在原始值不为Null或者空字符串的情况下会将基础类型T作为新的目标类型进行转换,否则直接返回Null。
为了验证上述这些类型转化规则,我们编写了如下的测试程序。如下面的代码片段所示,我们利用注册的MemoryConfigurationSource添加了三个配置项,对应的值分别为Null、空字符串和“123”,然后调用GetValue方法分别对它们进行类型转换,转换的目标类型分别是Object、Int32和Nullable<Int32>,上述的转换规则体现在对应的调试断言中。
public class Program
{
public static void Main()
{
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);
}
}
三、自定义TypeConverter
按照前面介绍的类型转换规则,如果目标类型支持源自字符串的类型转换,那么我们就能够将配置项的原始值绑定为该类型的对象,而让某个类型支持某种类型转换规则的途径就是为之注册相应的TypeConverter。如下面的代码片段所示,我们定义了一个表示二维坐标的Point对象,并为它注册了一个类型为PointTypeConverter的TypeConverter,PointTypeConverter通过实现的ConvertFrom方法将坐标的字符串表达式(比如“(123,456)”)转换成一个Point对象。
[TypeConverter(typeof(PointTypeConverter))]
public class Point
{
public double X { get; set; }
public double Y { get; set; }
} 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)
{
string[] split = value.ToString().Split(',');
double x = double.Parse(split[0].Trim().TrimStart('('));
double y = double.Parse(split[1].Trim().TrimEnd(')'));
return new Point { X = x, Y = y };
}
}
由于定义的Point类型支持源自字符串的类型转换,所以如果配置项的原始值(字符串)具有与之兼容的格式,我们将能按照如下的方式将它绑定为一个Point对象。(S608)
public class Program
{
public static void Main()
{
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);
}
}
四、绑定复合数据类型
这里所谓的复合类型表示一个具有属性数据成员的自定义类型。如果通过一颗树来表示一个复合对象,那么叶子节点承载所有的数据,并且叶子节点的数据类型均为基元类型。如果通过数据字典来提供一个复杂对象所有的原始数据,那么这个字典中只需要包含叶子节点对应的值即可。至于如何通过一个字典对象体现复合对象的结构,我们只需要将叶子节点所在的路径作为字典元素的Key就可以了。
public class Profile: IEquatable<Profile>
{
public Gender Gender { get; set; }
public int Age { get; set; }
public ContactInfo ContactInfo { get; set; } public Profile() {}
public Profile(Gender gender, int age, string emailAddress, string phoneNo)
{
Gender = gender;
Age = age;
ContactInfo = new ContactInfo
{
EmailAddress = emailAddress,
PhoneNo = phoneNo
};
}
public bool Equals(Profile other)
{
return other == null
? false
: Gende == other.Gender && Age == other.Age && ContactInfo.Equals(other.ContactInfo);
}
} public class ContactInfo: IEquatable<ContactInfo>
{
public string EmailAddress { get; set; }
public string PhoneNo { get; set; }
public bool Equals(ContactInfo other)
{
return other == null
? false
: EmailAddress == other.EmailAddress && PhoneNo == other.PhoneNo;
}
} public enum Gender
{
Male,
Female
}
如上面的代码片段所示,我们定义了一个表示个人基本信息的Profile类,它的三个属性(Gender、Age和ContactInfo)分别表示性别、年龄和联系方式。由于配置绑定会调用默认无参构造函数来创建绑定的目标对象,所以我们需要为Profile类型定义一个默认构造函数。表示联系信息的ContactInfo类型具有两个属性(EmailAddress和PhoneNo),它们分别表示电子邮箱地址和电话号码。一个完整的Profile对象可以通过如下图所示的树来体现。
如果需要通过配置的形式来表示一个完整的Profile对象,我们只需要将四个叶子节点(性别、年龄、电子邮箱地址和电话号码)对应的数据由配置来提供即可。对于承载配置数据的数据字典,我们需要按照如下表所示的方式将这四个叶子节点的路径作为字典元素的Key。
Key
|
Value |
Gender | Male |
Age | 18 |
ContactInfo:Email | foobar@outlook.com |
ContactInfo:PhoneNo | 123456789 |
我们通过如下的程序来验证针对复合数据类型的配置绑定。我们创建了一个ConfigurationBuilder对象并为它添加了一个MemoryConfigurationSource对象,它按照如上表所示的结构提供了原始的配置数据。在调用Build方法构建出IConfiguration对象之后,我们直接调用扩展方法Get<T>将它转换成一个Profile对象。
public class Program
{
public static void Main()
{
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.Equals( new Profile(Gender.Male, 18, "foobar@outlook.com", "123456789")));
}
}
五、绑定集合对象
如果配置绑定的目标类型是一个集合(包括数组),那么当前IConfiguration对象的每一个子配置节将绑定为集合的元素。假设我们需要将一个IConfiguration对象绑定为一个元素类型为Profile的集合,它表示的配置树应该具有如下图所示的结构。
既然我们能够正确将集合对象通过一个合法的配置树体现出来,那么我们就可以将它转换成配置字典。对于通过下表所示的这个包含三个元素的Profile集合,我们可以采用如下表所示的结构来定义对应的配置字典。
Key
|
Value |
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 | 40 |
baz:ContactInfo:EmailAddress | baz@outlook.com |
baz:ContactInfo:PhoneNo | 789 |
我们依然通过一个简单的实例来演示针对集合的配置绑定。如下面的代码片段所示,我们创建了一个ConfigurationBuilder对象并为它注册了一个MemoryConfigurationSource对象,它按照如s上表所示的结构提供了原始的配置数据。在得到这个ConfigurationBuilder对象创建的IConfiguration对象之后,我们两次调用其Get<T>方法将它分别绑定为一个IEnumerable<Profile>对象和一个Profile[] 数组。由于IConfigurationProvider通过GetChildKeys方法提供的Key是经过排序的,所以在绑定生成的集合或者数组中的元素的顺序与配置源是不相同的,如下的调试断言也体现了这一点。
public class Program
{
public static void Main()
{
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 configuration = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build(); var profiles = new Profile[]
{
new Profile(Gender.Male,18,"foo@outlook.com","123"),
new Profile(Gender.Male,25,"bar@outlook.com","456"),
new Profile(Gender.Female,36,"baz@outlook.com","789"),
}; var collection = configuration.Get<IEnumerable<Profile>>();
Debug.Assert(collection.Any(it => it.Equals(profiles[0])));
Debug.Assert(collection.Any(it => it.Equals(profiles[1])));
Debug.Assert(collection.Any(it => it.Equals(profiles[2]))); var array = configuration.Get<Profile[]>();
Debug.Assert(array[0].Equals(profiles[1]));
Debug.Assert(array[1].Equals(profiles[2]));
Debug.Assert(array[2].Equals(profiles[0]));
}
}
在针对集合类型的配置绑定过程中,如果某个配置节绑定失败,该配置节将被忽略并选择下一个配置节继续进行绑定。但是如果目标类型为数组,最终绑定生成的数组长度与子配置节的个数总是一致的,绑定失败的元素将被设置为Null。比如我们将上面的程序作了如下的改写,保存原始配置的字典对象包含两个元素,第一个元素的性别从“Male”改为“男”,毫无疑问这个值是不可能转换成Gender枚举对象的,所以针对这个Profile的配置绑定会失败。如果将目标类型设置为IEnumerable<Profile>,那么最终生成的集合只会有两个元素,倘若目标类型切换成Profile数组,数组的长度依然为3,但是第一个元素是Null。
public class Program
{
public static void Main()
{
var source = new Dictionary<string, string>
{
["foo:gender"] = "男",
["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 configuration = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build(); var collection = configuration.Get<IEnumerable<Profile>>();
Debug.Assert(collection.Count() == 2); var array = configuration.Get<Profile[]>();
Debug.Assert(array.Length == 3);
Debug.Assert(array[2] == null);
//由于配置节按照Key进行排序,绑定失败的配置节为最后一个
}
}
六、绑定字典
能够通过配置绑定生成的字典是一个实现了IDictionary<string,T>的类型,也就是说配置模型没有对字典的Value类型作任何要求,但是字典对象的Key必须是一个字符串(或者枚举)。如果采用配置树的形式来表示这么一个字典对象,我们会发现它与针对集合的配置树在结构上几乎是一样的。唯一的区别是集合元素的索引直接变成了字典元素的Key。
也就是说上图所示的这棵配置树同样可以表示成一个具有三个元素的Dictionary<string, Profile>对象 ,它们对应的Key分别是“Foo”、“Bar”和“Baz”,所以我们可以按照如下的方式将承载相同数据的IConfiguration对象绑定为一个IDictionary<string,T>对象。(S612)
public class Program
{
public static void Main()
{
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 configuration = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build(); var profiles = configuration.Get<IDictionary<string, Profile>>();
Debug.Assert(profiles["foo"].Equals( new Profile(Gender.Male, 18, "foo@outlook.com", "123")));
Debug.Assert(profiles["bar"].Equals( new Profile(Gender.Male, 25, "bar@outlook.com", "456")));
Debug.Assert(profiles["baz"].Equals( new Profile(Gender.Female, 36, "baz@outlook.com", "789")));
}
}
[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框架揭秘] 配置[4]:将配置绑定为对象的更多相关文章
- [ASP.NET Core 3框架揭秘] Options[1]: 配置选项的正确使用方式[上篇]
依赖注入不仅是支撑整个ASP.NET Core框架的基石,也是开发ASP.NET Core应用采用的基本编程模式,所以依赖注入十分重要.依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式 ...
- [ASP.NET Core 3框架揭秘] Options[2]: 配置选项的正确使用方式[下篇]
四.直接初始化Options对象 前面演示的几个实例具有一个共同的特征,即都采用配置系统来提供绑定Options对象的原始数据,实际上,Options框架具有一个完全独立的模型,可以称为Options ...
- [ASP.NET Core 3框架揭秘] 配置[1]:读取配置数据[上篇]
提到"配置"二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...
- [ASP.NET Core 3框架揭秘] 配置[2]:读取配置数据[下篇]
[接上篇]提到“配置”二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义 ...
- [ASP.NET Core 3框架揭秘] 配置[5]:配置数据与数据源的实时同步
在<配置模型总体设计>介绍配置模型核心对象的时候,我们刻意回避了与配置同步相关的API,现在我们利用一个独立文章来专门讨论这个话题.配置的同步涉及到两个方面:第一,对原始的配置源实施监控并 ...
- [ASP.NET Core 3框架揭秘] 配置[3]:配置模型总体设计
在<读取配置数据>([上篇],[下篇])上面一节中,我们通过实例的方式演示了几种典型的配置读取方式,接下来我们从设计的维度来重写认识配置模型.配置的编程模型涉及到三个核心对象,分别通过三个 ...
- [ASP.NET Core 3框架揭秘] 配置[7]:多样化的配置源[中篇]
物理文件是我们最常用到的原始配置载体,而最佳的配置文件格式主要有三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...
- [ASP.NET Core 3框架揭秘] 配置[6]:多样化的配置源[上篇]
.NET Core采用的这个全新的配置模型的一个主要的特点就是对多种不同配置源的支持.我们可以将内存变量.命令行参数.环境变量和物理文件作为原始配置数据的来源.如果采用物理文件作为配置源,我们可以选择 ...
- ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式
.NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...
随机推荐
- 【Elasticsearch 7 探索之路】(四)Analyzer 分析
上一篇,什么是倒排索引以及原理是什么.本篇讲解 Analyzer,了解 Analyzer 是什么 ,分词器是什么,以及 Elasticsearch 内置的分词器,最后再讲解中文分词是怎么做的. 一.A ...
- 新闻实时分析系统Hive与HBase集成进行数据分析 Cloudera HUE大数据可视化分析
1.Hue 概述及版本下载 1)概述 Hue是一个开源的Apache Hadoop UI系统,最早是由Cloudera Desktop演化而来,由Cloudera贡献给开源社区,它是基于Python ...
- WebGL简易教程(十三):帧缓存对象(离屏渲染)
目录 1. 概述 2. 示例 2.1. 着色器部分 2.2. 初始化/准备工作 2.2.1. 着色器切换 2.2.2. 帧缓冲区 2.3. 绘制函数 2.3.1. 初始化顶点数组 2.3.2. 传递非 ...
- Scala学习系列一
一 scala介绍 Scala是一门以java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言. 1) Scala 是一门多范式 (multi-parad ...
- linux bash shell编程之参数变量和流程控制。
参数变量:用来向脚本中传递参数 我们在执行脚本的时候可以在其后面加入一些参数,通常来说这些参数与脚本中变量为对应关系. start.sh argu1 argu2 引用方式: $1,,2,……${10} ...
- 关于servlet报错和jsp中报关于servlet的错误
servlet-api是对servlet的支持,如果你导入别人的项目后出现servlet中的导包处出现关于javax.servlet.的错误,那么就是缺少这个包了.还有对jsp页面中的报错的支持. 下 ...
- Mac从拆箱到入门
Mac从拆箱到入门 记录首次使用Mac的我的历程,不是专业的Mac使用教程,只是简单的记录.还有我在使用过程中一些用到的功能都一些小提示吧. 1.首次开机配置,对于一个完全的新手来说(也就是我) ...
- element中 input赋值后无法再次输入值
项目中有个需求,在表格里点击某条数据弹出窗口进行修改值,当时弹出的是input上进行修改,所以当我点击数据的时候,先进行回显原先的数据,再进行修改. 点击某条数据,弹出窗口,进行后台请求,将后台返回的 ...
- 使用Jq实现弹出层
对于前端程序员来说,弹出层是经常用到的,下面我叫大家如何用实现JQuery实现弹出层的效果,代码如下: CSS:弹出层的效果 .mask { position: absolute; top: 0px; ...
- 漫谈LiteOS之开发板-串口(基于GD32450i-EVAL)
[摘要] 主要讲解物联网的技术积累,本期我们先带领大家学习漫谈LiteOS之漫谈开发板第一集-串口,本文基于GD32450i-EVAL对串口以及其通信做了一个简要的分析,以及开发过程中遇到的一些技术 ...