環境:
- 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文件。