.Net Core中的配置檔案原始碼解析
一、配置簡述
之前在.Net Framework平臺開發時,一般配置檔案都是xml格式的Web.config,而需要配置其他格式的檔案就需要自己去讀取內容,載入配置了。.而Net Core支援從命令列、環境變數、檔案、記憶體、Key-per-file中載入配置,其中檔案包括xml、ini、json三種檔案格式。這裡需要說明一下,不論哪種格式的配置檔案,載入到程式中最終會以Key/Value形式儲存,原始碼中將所有配置讀取出來並儲存在 Dictionary<string, string> Data 的字典中。額外說明一下Key-per-file配置方式是以檔名稱為key,檔案內容為value的形式。
二、原始碼解析
點選檢視原始碼,我畫了一個主要類之間的邏輯關係圖,如下:
實線表示繼承關係,每條虛線表示意義已經在上面表明。可以分為4個部分,最終要構建的就是IConfigurationRoot,因為所有的配置都儲存在它裡面的Providers集合中的Data的字典中。
構建它兩條路徑,一條通過IConfigurationSource構建IConfigurationProvider,然後通過IConfigurationProvider集合構建IConfigurationRoot,也就是圖上標記的1/2兩步。
另一條是通過IConfigurationSource集合構建IConfigurationBuilder,在通過Builder方法遍歷迴圈建立建立IConfigurationProvider集合,在通過IConfigurationProvider集合構建IConfigurationRoot。也就是圖上的3/4兩步。
這裡只講檔案配置方式,包括xml、ini、json檔案。
IConfigurationSource:
它是配置檔案的根本,它表示配置檔案本身,比如繼承自它的 FileConfigurationSource 裡面有個Path屬性,表示檔案路徑,會通過這個Path讀取這個檔案內容。如我們常用的xml、json、ini檔案配置都繼承自 FileConfigurationSource 。
IConfigurationProvider:
前面說了,配置最終會轉換為key/value的形式,而 IConfigurationProvider 就是這個裝換。比如繼承他的 FileConfigurationProvider ,它的建構函式需要傳第一個 FileConfigurationSource , FileConfigurationProvider 會根據 FileConfigurationSource 的 path 屬性找到對應檔案,然後讀取配置檔案到它的 IDictionary<string, string> Data 中。xml的讀取在 XmlConfigurationProvider 中,json檔案的讀取在 JsonConfigurationProvider 。
private void Load(bool reload) { //刪除一些判斷邏輯 if (reload) { Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } using (var stream = file.CreateReadStream()) { //這一步會將檔案裡面的配置 放到Data中 不同的檔案格式Load方法都有對應的實現,這裡就不細看了。 Load(stream); } OnReload(); }
一個 IConfigurationProvider 的實現類中會包含一個對應的 IConfigurationSource 的實現類,比如 JsonConfigurationProvider 類中包含一個 JsonConfigurationProvider
IConfigurationRoot:
所有的類最終構建的目標就是IConfigurationRoot,它包含一個 IList<IConfigurationProvider> _providers 集合,而每一個 IConfigurationProvider 包含一個 IDictionary<string, string> Data ,所以現在你現在是不是有提花灌頂的感覺。這樣做的目的是一個程式可能有多個配置檔案,可能有一個xml檔案、一個json檔案、一個Ini檔案。每個配置檔案會被讀取到對應的 ConfigurationSource 中,然後通過它構建對應的 ConfigurationProvider ,然後用三個 ConfigurationProvider 構建 ConfigurationRoot 。
那這樣會有一個問題,比如xml檔案配置了 A的值為1,json檔案也配置名稱為A的值為2,那會獲取誰的值呢? 下面原始碼可以看到,IConfigurationRoot會反轉新增的順序,迴圈遍歷,如果找到就返回,所以後面新增的會覆蓋前面新增的。
public string this[string key] { get { //_providers也就是IConfigurationRoot裡面的IList<IConfigurationProvider> foreach (var provider in _providers.Reverse()) { string value; if (provider.TryGet(key, out value)) { return value; } } return null; } set { if (!_providers.Any()) { throw new InvalidOperationException(Resources.Error_NoSources); } foreach (var provider in _providers) { provider.Set(key, value); } } }
對於Json、xml、ini檔案,比如
{ "user": { "address": { "Provice": "浙江省", "city": "杭州市" }, "name": "張三" } }
在Data中儲存的key為 user:address:Province、user:address:city和user:name。中間以“:”分割,這個“:”是固定只讀的,不可以改變的。
三、簡單使用
建立一個WebApi專案,修改Program程式碼如下:
public static void Main(string[] args) { var configRoot = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory())//設定基礎路徑 .AddJsonFile("a.json")//載入配置 .AddXmlFile("b.xml") .Build(); var builder = new WebHostBuilder() .UseConfiguration(configRoot) .UseContentRoot(Directory.GetCurrentDirectory()) .UseKestrel() .UseStartup<Startup>() .Build(); builder.Run(); }
然後當用的時候,只需要注入 IConfiguration ,就能獲取所有的配置。