1. 程式人生 > >.Net Core中的配置檔案原始碼解析

.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 ,就能獲取所有的配置。