环境:
- win10
- vs2019.16.5.1
- dnSpy v6.1.4 (.NET Core)
- ILSpy版本6.0.0.5559-preview2
一、.netcore中的配置概念及简单用法
.netcore中的配置模块可以将你的配置文件(如:json、xml、ini等)自动读取成一个树状结构(逻辑上是树状,实际上是扁平化的),这样你就可以方便快捷的获取配置数据了。它不仅广泛应用在asp.net core中,在.netcore中也可以方便的使用,后面讲的内容都是在.netcore上面进行试验分析的。
配置IConfiguration能做什么?
可以从六大配置源(内存、json文件、xml文件、ini文件、命令行参数和环境变量)中读取配置内容。
配置的特点是什么?
1). 树状结构(逻辑上):
当配置模块完成配置构建后会返回一个树状结构,这个树状结构我们称之为IConfiguration(IConfigurationRoot和IConfigurationSection都继承自IConfiguration),它的根节点是IConfigurationRoot、子节点是IConfigurationSection。子节点里封装了三个属性(Key、Path、Value),这种树状结构和注册表的组织形式很像。
2). 后来者居上:
可以组合多种数据来源的配置(比如:将json文件和环境变量的数据一起作为配置),当你获取配置项的时候,它会从最后加入的配置源中开始寻找。
1.1 配置使用示例(从内存和json中读取配置)
下面的代码演示了配置的两个特点(树状结构和后来者居上):
安装nuget包Microsoft.Extensions.Configuration.Json
在Program中写下代码:
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
builder.AddJsonFile("test.json", true, true);
builder.AddInMemoryCollection(new Dictionary<string, string>()
{
{ "age","20"},
{ "score:eng","89"},
{ "score:math","98"},
{ "score:math:select","30"},
{ "score:math:ask","68"}
});
IConfiguration conf = builder.Build();
Console.WriteLine($"ConfigRoot\t姓名={conf["name"]},年龄={conf["age"]},性别={conf["sex"]},数学成绩={conf["score:math"]}(选择={conf["score:math:select"]},问答={conf["score:math:ask"]}),英语成绩:{conf["score:eng"]}");
Console.WriteLine("ok");
Console.ReadLine();
return;
}
}
}
配置文件如下:
直接运行如下:
从上面的代码中应该能体会到配置的树状结构(层级节点之间固定以":"划分)和配置的后来者居上特点了。
1.2 从其他地方读取配置
1.2.1 从xml文件读取配置
安装包:Microsoft.Extensions.Configuration.Xml
Program中代码:
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
builder.AddXmlFile("test.xml", true, true);
var conf = builder.Build();
Console.WriteLine($"姓名={conf["name"]},年龄={conf["age"]},数学成绩={conf["score:math"]},英语={conf["score:eng"]}");
Console.WriteLine("Hello World!");
}
}
}
配置文件test.xml:
直接运行输出:
1.2.2 从ini文件读取配置
安装包:Microsoft.Extensions.Configuration.Ini
Program代码:
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
builder.AddIniFile("test.ini", true, true);
var conf = builder.Build();
Console.WriteLine($"姓名={conf["name"]},年龄={conf["age"]},数学成绩={conf["score:math"]},英语={conf["score:eng"]},性别={conf["ext:info:sex"]},地址={conf["ext:info:addr"]}");
Console.WriteLine("Hello World!");
}
}
}
配置文件test.ini:
直接运行输出:
1.2.3 从命令行读取配置
安装包:Microsoft.Extensions.Configuration.CommandLine
Program代码:
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
builder.AddCommandLine(args);
var conf = builder.Build();
Console.WriteLine($"姓名={conf["name"]},年龄={conf["age"]},数学成绩={conf["score:math"]},英语={conf["score:eng"]}");
Console.WriteLine("Hello World!");
Console.ReadLine();
}
}
}
发布后在命令行中测试如下:
1.2.4 从环境变量中读取配置
安装包:Microsoft.Extensions.Configuration.EnvironmentVariables
Program代码:
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
builder.AddEnvironmentVariables("test_conf_");
var conf = builder.Build();
Console.WriteLine($"姓名={conf["name"]},年龄={conf["age"]},数学成绩={conf["score:math"]},英语={conf["score:eng"]}");
Console.WriteLine("Hello World!");
Console.ReadLine();
}
}
}
在电脑上设置环境变量如下:
设置好之后你可能需要重新发布后再运行,或者是重启电脑。。。
运行的效果如下:
1.3 相关的nuget包之间关系
上面使用到的主要nuget包的依赖关系如下:
二、从源码角度分析
2.1 包:Microsoft.Extensions.Configuration.Abstractions
这个是配置最基础的包,里面定义了6大接口和3个类,我们在ILSpy中看一下:
这里面的6个接口也定义了配置的基本组织结构,那么这几个接口之间的职责是怎么划分的呢?
首先,我们看一下Build后的配置树模型:
上图显示的是配置树的模型以及三个接口IConfiguration
、IConfigurationRoot
和IConfigurationSection
之间的关系,为了验证上图所示,我们看下源码:
public interface IConfiguration
{
string this[string key]
{
get;
set;
}
IConfigurationSection GetSection(string key);
IEnumerable<IConfigurationSection> GetChildren();
IChangeToken GetReloadToken();
}
public interface IConfigurationRoot : IConfiguration
{
IEnumerable<IConfigurationProvider> Providers{ get;}
void Reload();
}
public interface IConfigurationSection : IConfiguration
{
//这个key值和path值不同,key是不带路径前缀,path是带上路径前缀
string Key{ get;}
string Path{ get;}
string Value{ get;set;}
}
上图中分析的是构建完成后的结果,那么还有三个接口是负责构建配置树的,这三个接口如下:
从上图中也可以看到,这三个接口的职责:
IConfigurationBuilder: 给最终用户使用,负责搜集配置源(IConfigurationSource),搜集完成后通过Build方法构建出IConfigurationRoot
IConfigurationSource: 代表配置源,最终用户将组装好的配置源交给IConfigurationBuilder去管理。
IConfigurationProvider: 配置提供对象,IConfigurationBuilder在进行Build过程的时候会调用它内部的IConfigurationSource的Build,而IConfigurationSource的Build的返回就是这个IConfigurationProvider。这个IConfigurationProvider最终放进了IConfigurationRoot内部,这样当我们从IConfigurationRoot获取配置数据的时候就是从IConfigurationProvider里面搜索数据。
其他三个接口(IConfigurationBuilder、IConfigurationSource和IConfigurationProvider)的源码如下:
public interface IConfigurationBuilder
{
IDictionary<string, object> Properties{ get;}
IList<IConfigurationSource> Sources{ get;}
IConfigurationBuilder Add(IConfigurationSource source);
IConfigurationRoot Build();
}
public interface IConfigurationSource
{
IConfigurationProvider Build(IConfigurationBuilder builder);
}
public interface IConfigurationProvider
{
bool TryGet(string key, out string value);
void Set(string key, string value);
IChangeToken GetReloadToken();
void Load();
IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath);
}
理解了上面六大接口各自的作用和职责后,我们接着看包:Microsoft.Extensions.Configuration
2.2 包:Microsoft.Extensions.Configuration
我们还是现在ILSpy里看一下:
从上图可以看到,这个包里首先对前一个包定义的接口进行了实现,然后又增加了两个类以支持从内存中读取配置。
现在我们来看一下接口的实现情况:
1. ConfigurationRoot:
/*
这个类代表的是配置树的根节点,它里面存储着一系列已生成的Provider,当接收到读取配置请求的时候就会从这些Provider中倒序查找
*/
public class ConfigurationRoot : IConfigurationRoot, IConfiguration, IDisposable
{
private readonly IList<IConfigurationProvider> _providers;
private readonly IList<IDisposable> _changeTokenRegistrations;
private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
public IEnumerable<IConfigurationProvider> Providers => _providers;
public string this[string key]
{
get
{
for (int num = _providers.Count - 1; num >= 0; num--)
{
if (_providers[num].TryGet(key, out string value))
{
return value;
}
}
return null;
}
set
{
if (!_providers.Any())
{
throw new InvalidOperationException(Resources.Error_NoSources);
}
foreach (IConfigurationProvider provider in _providers)
{
provider.Set(key, value);
}
}
}
//构造函数:由IConfigurationBuilder调用
public ConfigurationRoot(IList<IConfigurationProvider> providers)
{
if (providers == null)
{
throw new ArgumentNullException("providers");
}
_providers = providers;
_changeTokenRegistrations = new List<IDisposable>(providers.Count);
foreach (IConfigurationProvider p in providers)
{
p.Load();
_changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), delegate
{
RaiseChanged();
}));
}
}
public IEnumerable<IConfigurationSection> GetChildren()
{
return this.GetChildrenImplementation(null);
}
public IChangeToken GetReloadToken()
{
return _changeToken;
}
public IConfigurationSection GetSection(string key)
{
return new ConfigurationSection(this, key);
}
public void Reload()
{
foreach (IConfigurationProvider provider in _providers)
{
provider.Load();
}
RaiseChanged();
}
private void RaiseChanged()
{
Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()).OnReload();
}
public void Dispose()
{
foreach (IDisposable changeTokenRegistration in _changeTokenRegistrations)
{
changeTokenRegistration.Dispose();
}
foreach (IConfigurationProvider provider in _providers)
{
(provider as IDisposable)?.Dispose();
}
}
}
2. ConfigurationSection:
/*
这个类代表的是配置树的节点,本身并不存储provider,所以,所有企图从ConfigurationSection中获取配置的请求都会被转到ConfigurationRoot执行。
*/
public class ConfigurationSection : IConfigurationSection, IConfiguration
{
private readonly IConfigurationRoot _root;
private readonly string _path;
private string _key;
public string Path => _path;
public string Key
{
get
{
if (_key == null)
{
_key = ConfigurationPath.GetSectionKey(_path);
}
return _key;
}
}
public string Value
{
get
{
return _root[Path];
}
set
{
_root[Path] = value;
}
}
public string this[string key]
{
get
{
return _root[ConfigurationPath.Combine(Path, key)];
}
set
{
_root[ConfigurationPath.Combine(Path, key)] = value;
}
}
public ConfigurationSection(IConfigurationRoot root, string path)
{
if (root == null)
{
throw new ArgumentNullException("root");
}
if (path == null)
{
throw new ArgumentNullException("path");
}
_root = root;
_path = path;
}
public IConfigurationSection GetSection(string key)
{
return _root.GetSection(ConfigurationPath.Combine(Path, key));
}
public IEnumerable<IConfigurationSection> GetChildren()
{
return _root.GetChildrenImplementation(Path);
}
public IChangeToken GetReloadToken()
{
return _root.GetReloadToken();
}
}
3. ConfigurationBuilder:
/*
这个类负责搜集配置源,并将搜到的配置源转换为provider,最后将这些provider放进新创建的IConfigurationRoot中
*/
public class ConfigurationBuilder : IConfigurationBuilder
{
public IList<IConfigurationSource> Sources
{
get;
} = new List<IConfigurationSource>();
public IDictionary<string, object> Properties
{
get;
} = new Dictionary<string, object>();
public IConfigurationBuilder Add(IConfigurationSource source)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
Sources.Add(source);
return this;
}
public IConfigurationRoot Build()
{
List<IConfigurationProvider> list = new List<IConfigurationProvider>();
foreach (IConfigurationSource source in Sources)
{
IConfigurationProvider item = source.Build(this);
list.Add(item);
}
return new ConfigurationRoot(list);
}
}
4. ConfigurationProvider:
注意:这是一个抽象类。
这个基类把配置源里面的数据都放在了一个字典(IDictionary<string, string> Data)中,这样凡是需要获取配置数据的时候就会遍历这个字典,找到后就返回。
public abstract class ConfigurationProvider : IConfigurationProvider
{
private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
protected IDictionary<string, string> Data
{
get;
set;
}
protected ConfigurationProvider()
{
Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public virtual bool TryGet(string key, out string value)
{
return Data.TryGetValue(key, out value);
}
public virtual void Set(string key, string value)
{
Data[key] = value;
}
public virtual void Load()
{
}
public virtual IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath)
{
string prefix = (parentPath == null) ? string.Empty : (parentPath + ConfigurationPath.KeyDelimiter);
return (from kv in Data
where kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
select Segment(kv.Key, prefix.Length)).Concat(earlierKeys).OrderBy((string k) => k, ConfigurationKeyComparer.Instance);
}
private static string Segment(string key, int prefixLength)
{
int num = key.IndexOf(ConfigurationPath.KeyDelimiter, prefixLength, StringComparison.OrdinalIgnoreCase);
if (num >= 0)
{
return key.Substring(prefixLength, num - prefixLength);
}
return key.Substring(prefixLength);
}
public IChangeToken GetReloadToken()
{
return _reloadToken;
}
protected void OnReload()
{
Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken()).OnReload();
}
public override string ToString()
{
return GetType().Name ?? "";
}
}
上面列举了基础的接口实现,那么还剩下具体的IConfigurationProvider和IConfigurationSource的实现,我们这就以从内存中读取配置为例来看一下具体的实现:
5. MemoryConfigurationSource:
/*
内存中的配置源,简单的不能再简单了。
*/
public class MemoryConfigurationSource : IConfigurationSource
{
public IEnumerable<KeyValuePair<string, string>> InitialData
{
get;
set;
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new MemoryConfigurationProvider(this);
}
}
6. MemoryConfigurationProvider:
/*
内存中的配置提供程序,也是简单的不能再简单了。
*/
public class MemoryConfigurationProvider : ConfigurationProvider, IEnumerable<KeyValuePair<string, string>>, IEnumerable
{
private readonly MemoryConfigurationSource _source;
public MemoryConfigurationProvider(MemoryConfigurationSource source)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
_source = source;
if (_source.InitialData != null)
{
foreach (KeyValuePair<string, string> initialDatum in _source.InitialData)
{
base.Data.Add(initialDatum.Key, initialDatum.Value);
}
}
}
public void Add(string key, string value)
{
base.Data.Add(key, value);
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return base.Data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
到此为止,我们就把.netcore中关于配置最核心的部分讲完了(其实代码量很小,关键是读懂各对象之间的关系和职责)。
2.3 包:Microsoft.Extensions.Configuration.Json等
包Microsoft.Extensions.Configuration.Json里面主要是对IConfigurationProvider和IConfigurationSource的实现,提供了对json文件读取的能力。其他的几个包的功能也类似,这里不再赘述。
三、配置文件转换规则
在.netcore中,其他的配置提供程序(包括ini、json、xml、环境变量、命令行)最终都转化为扁平化的键值对数据,不过它们的键都以":"做分隔,所以从逻辑上来看它们组成了一个树状机构(但实际上它们是扁平化的)。那么,从其他配置提供程序转换到这种扁平化结构的规则是什么呢?
3.1 ini配置文件
.ini 文件是Initialization File的缩写,即初始化文件,是windows的系统配置文件所采用的存储格式。
需要注意的地方如下:
- INI文件由节、键、值组成。
- INI文件中的行注释以";"、"#“或”/“开头,不过建议以”;"开头:
- INI文件中的值包含空格的话要用双引号,否则会忽略空格。
INI文件示例如下:
因为ini文件本身的扁平化程度很高,所以转换规则很简单,基本“既见既所得”:
3.2 xml配置文件
xml文件的转换规则如下:
- 忽略根节点,即:你可以任意命名根节点
- 不支持xml元素的命名空间(这里也同时限制住了:元素和属性名称不能带前缀),如下图所示:
- 相邻节点不允许出现同名的元素,除非它们具有不同的name属性值(name属性值将作为key值的一部分),看下图所示:
3.3 命令行配置参数
因为命令行参数本来就是扁平化的,所以不存在转化的说法,下面探讨一下 “在命令行中输入一行命令,程序运行后是接收到怎样的参数呢?”
基于上图中的分析,我们也可以在程序中伪造命令行参数,看下图所示:
3.4 json配置文件
json文件的转换规则如下:
- 最外层必须是"{}",不能是"[]",看下图所示:
- 对于内层出现的"[]",对里面的元素进行编号,如下图所示:
- 未免引起不必要的麻烦,不要在属性中使用":"。
四、从配置到模型的绑定
虽然我们可以从IConfiguration中轻松的取出配置值,但是我们更倾向于将其转换成一个POCO对象(Plain Old C# Object:没有继承,没有其他类入侵,仅仅是封装了属性,相当于java中的POJO),以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定。
从上面的分析我们知道,无论配置的来源是什么,它最终都被转化为一个字典(以":"分隔,逻辑化的树状结构),那么配置绑定也就是将这个字典的一部分反序列化为一个POCO对象。
4.1 配置绑定的简单应用示例
- 1 安装nuget包:
Microsoft.Extensions.Configuration.Binder
- 2 测试代码如下(使用内存配置源):
class Program { static void Main(string[] args) { var conf = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary<string, string>() { {"name","小明" }, { "age","20"} }).Build(); var person = conf.Get<Person>(); Console.WriteLine($"person.name={person.name},person.age={person.age}"); Console.WriteLine("Hello World!"); Console.ReadLine(); } } public class Person { public string name { get; set; } public string age { get; set; } }
- 3 运行效果如下:
4.2 复合对象的绑定
将上面的代码替换如下:
class Program
{
static void Main(string[] args)
{
var conf = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>()
{
{"name","老王" },
{ "age","45"},
{"id","2" },
{"sex","Male" },
{"isDuty","true" },
{"birth","1968-02-05" },
{"hasLeave","false" },
{"students:0:id","3" },
{"students:0:name","小明" },
{"students:0:age","13" },
{"students:0:score","98.23" },
{"students:0:hasLeave","true" },
{"students:1:id","4" },
{"students:1:name","小红" },
{"students:1:age","15" },
{"students:1:score","89" }
}).Build();
var person = conf.Get<Teacher>();
Console.WriteLine($"person.name={person.name},person.age={person.age}");
Console.WriteLine("Hello World!");
Console.ReadLine();
}
}
public class Person
{
public int id { get; set; }
public string name { get; set; }
public int age { get; set; }
public DateTime? birth { get; set; }
public bool hasLeave { set; get; } = false;
}
public class Teacher : Person
{
public bool isDuty { get; set; }
public EnumSex sex { get; set; }
public List<Student> students { set; get; }
}
public enum EnumSex
{
Male,
Female
}
public class Student : Person
{
public double score { get; set; }
}
运行效果如下:
4.3 自定义绑定转换器
将上面的代码替换如下:
[TypeConverter(typeof(PointConvertor))]
public class Point
{
public double X { set; get; }
public double Y { set; get; }
}
public class PointConvertor : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
try
{
string str = value.ToString();
var arr = str.Split(',');
var x = double.Parse(arr[0]);
var y = double.Parse(arr[1]);
return new Point()
{
X = x,
Y = y
};
}
catch (Exception ex)
{
Console.WriteLine($"转换出错:{ex?.Message}");
return null;
}
}
}
public class Person
{
public string name { get; set; }
public int age { get; set; }
public Point pos { set; get; }
}
class Program
{
public static void Main(string[] args)
{
var conf = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>()
{
["root:appname"] = "测试应用",
["root:appversion"] = "v1.0.0",
["root:person:name"] = "小明",
["root:person:age"] = "28",
["root:person:pos"] = "113.456784,27.234562"
}).Build();
var person = conf.GetSection("root:person").Get<Person>();
Console.WriteLine("ok");
Console.ReadLine();
}
}
运行效果:
4.4 绑定到集合
将上面的代码替换如下:
public class Person
{
public string name { get; set; }
public int id { get; set; }
public int age { get; set; }
public override string ToString()
{
return $"({id},name={name},age={age})";
}
}
class Program
{
public static void Main(string[] args)
{
var conf = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>()
{
["root:appname"] = "测试应用",
["root:appversion"] = "v1.0.0",
["root:person:小明:name"] = "小明",
["root:person:小明:age"] = "28",
["root:person:小明:id"] = "2",
["root:person:小红:name"] = "小红",
["root:person:小红:age"] = "24",
["root:person:小红:id"] = "3",
["root:student:0:id"] = "4",
["root:student:0:name"] = "张三",
["root:student:0:age"] = "10",
["root:student:1:id"] = "5",
["root:student:1:name"] = "李四",
["root:student:1:age"] = "11"
}).Build();
var persons = conf.GetSection("root:person").Get<List<Person>>();
var msg = "";
persons.ForEach(i => msg += (msg == "" ? (i.ToString()) : ("," + i.ToString())));
Console.WriteLine($"persons.Count={persons.Count},persons={msg}");
var students = conf.GetSection("root:student").Get<List<Person>>();
msg = "";
students.ForEach(i => msg += (msg == "" ? (i.ToString()) : ("," + i.ToString())));
Console.WriteLine($"students.Count={students.Count},students={msg}");
Console.WriteLine("ok");
Console.ReadLine();
}
}
运行效果:
4.5 绑定到字典
将上面的代码替换如下:
public class Person
{
public string name { get; set; }
public int id { get; set; }
public int age { get; set; }
public override string ToString()
{
return $"({id},name={name},age={age})";
}
}
class Program
{
public static void Main(string[] args)
{
var conf = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>()
{
["root:appname"] = "测试应用",
["root:appversion"] = "v1.0.0",
["root:person:小明:name"] = "小明",
["root:person:小明:age"] = "28",
["root:person:小明:id"] = "2",
["root:person:小红:name"] = "小红",
["root:person:小红:age"] = "24",
["root:person:小红:id"] = "3",
["root:student:0:id"] = "4",
["root:student:0:name"] = "张三",
["root:student:0:age"] = "10",
["root:student:1:id"] = "5",
["root:student:1:name"] = "李四",
["root:student:1:age"] = "11"
}).Build();
var dics = conf.GetSection("root:student").Get<IDictionary<string, Person>>();
var msg = "";
dics.Keys.ToList().ForEach(i => msg += (msg == "" ? (i + "=" + dics[i]) : ("," + i + "=" + dics[i])));
Console.WriteLine($"dics.Count={dics.Count},[{msg}]");
var dics2 = conf.GetSection("root:person").Get<IDictionary<string, Person>>();
msg = "";
dics2.Keys.ToList().ForEach(i => msg += (msg == "" ? (i + "=" + dics2[i]) : ("," + i + "=" + dics2[i])));
Console.WriteLine($"dics2.Count={dics2.Count},[{msg}]");
Console.WriteLine("ok");
Console.ReadLine();
}
}
运行效果:
4.6 将json配置文件绑定到含有集合、字典以及多维数组的对象上
将上面的代码修改如下:
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
namespace ConsoleApp7
{
public class Person
{
public string name { get; set; }
public int id { get; set; }
public int age { get; set; }
public List<Student> Students { set; get; }
public Dictionary<string, Student> dics { set; get; }
public int[][][] test { set; get; }
public static string ConvertArrString(int[][][] arr)
{
var msg = "";
msg += "[";
for (int i = 0; i < arr.Length; i++)
{
if (i > 0)
{
msg += ",";
}
msg += "[";
for (int j = 0; j < arr[i].Length; j++)
{
if (j > 0)
{
msg += ",";
}
msg += "[";
for (int k = 0; k < arr[i][j].Length; k++)
{
if (k > 0)
{
msg += ",";
}
msg += arr[i][j][k];
}
msg += "]";
}
msg += "]";
}
msg += "]";
return msg;
}
}
public class Student
{
public string name { get; set; }
public int id { get; set; }
public int age { get; set; }
public override string ToString()
{
return $"({id},name={name},age={age})";
}
}
class Program
{
public static void Main(string[] args)
{
var conf = new ConfigurationBuilder().AddJsonFile("test.json").Build();
var person = conf.GetSection("person").Get<Person>();
var msg = "";
Console.WriteLine($"person.id={person.id},person.name={person.name},person.age={person.age},person.Students.Count={person.Students.Count},person.dics.Count={person.dics.Count},person.test.length={person.test.Length}");
Console.WriteLine($"students=[{person.Students[0]},{person.Students[1]}]");
Console.WriteLine($"dics=[{person.dics["王五"]},{person.dics["赵六"]}]");
Console.WriteLine($"test={Person.ConvertArrString(person.test)}");
Console.WriteLine("ok");
Console.ReadLine();
}
}
}
对应的json文件如下:
{
"person": {
"id": 1,
"name": "小红",
"age": 25,
"Students": [
{
"id": 2,
"name": "张三",
"age": 18
},
{
"id": 3,
"na:me": "李四",
"age": 20
}
],
"dics": {
"王五": {
"id": 4,
"name": "王五",
"age": 22
},
"赵六": {
"id": 5,
"name": "赵六",
"age": 19
}
},
"test": [
[
[ 1, 2 ],
[ 3 ]
],
[
[ 4 ],
[ 5 ]
],
[
[ 6, 7, 8 ],
[ 9 ]
]
]
}
}
运行效果:
4.7 将xml配置文件绑定到含有集合、字典以及多维数组的对象上
将上面的代码替换如下:
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
namespace ConsoleApp7
{
public class Person
{
public string name { get; set; }
public int id { get; set; }
public int age { get; set; }
public List<Student> Students { set; get; }
public Dictionary<string, Student> dics { set; get; }
public int[][][] test { set; get; }
public static string ConvertArrString(int[][][] arr)
{
var msg = "";
msg += "[";
for (int i = 0; i < arr.Length; i++)
{
if (i > 0)
{
msg += ",";
}
msg += "[";
for (int j = 0; j < arr[i].Length; j++)
{
if (j > 0)
{
msg += ",";
}
msg += "[";
for (int k = 0; k < arr[i][j].Length; k++)
{
if (k > 0)
{
msg += ",";
}
msg += arr[i][j][k];
}
msg += "]";
}
msg += "]";
}
msg += "]";
return msg;
}
}
public class Student
{
public string name { get; set; }
public int id { get; set; }
public int age { get; set; }
public override string ToString()
{
return $"({id},name={name},age={age})";
}
}
class Program
{
public static void Main(string[] args)
{
var conf = new ConfigurationBuilder().AddXmlFile("test.xml").Build();
var person = conf.Get<Person>();
var msg = "";
Console.WriteLine($"person.id={person.id},person.name={person.name},person.age={person.age},person.Students.Count={person.Students.Count},person.dics.Count={person.dics.Count},person.test.length={person.test?.Length}");
Console.WriteLine($"students=[{person.Students[0]},{person.Students[1]}]");
Console.WriteLine($"dics=[{person.dics["王五"]},{person.dics["赵六"]}]");
Console.WriteLine("ok");
Console.ReadLine();
}
}
}
对应的xml文件如下:
<?xml version="1.0" encoding="utf-8"?>
<root>
<name>小明</name>
<age>28</age>
<id>1</id>
<Students name="张三">
<age>18</age>
<id>2</id>
</Students>
<Students name="李四" age="20" id="3" />
<dics name="王五" age="20" id="4" />
<dics name="赵六" age="20" id="5" />
<test>没能表示出来多维数组</test>
</root>
运行效果:
可以看到,在表示集合方面xml文件具有明显的劣势。所以能用json配置文件的,一般不要使用xml文件。