.netcore入门25:.net core源码分析之配置模块(IConfiguration)

环境:

  • win10
  • vs2019.16.5.1
  • dnSpy v6.1.4 (.NET Core)
  • ILSpy版本6.0.0.5559-preview2

参考:
官方文档:ASP.NET Core 中的配置

一、.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后的配置树模型:
在这里插入图片描述
上图显示的是配置树的模型以及三个接口IConfigurationIConfigurationRootIConfigurationSection之间的关系,为了验证上图所示,我们看下源码:

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文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章