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

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