.net 溫故知新:【8】.NET 中的設定從xml轉向json

2022-11-03 15:01:17

一、設定概述

在.net framework平臺中我們常見的也是最熟悉的就是.config檔案作為設定,控制檯桌面程式是App.config,Web就是web.config,裡面的設定格式為xml格式。

在xml裡面有系統生成的設定項,也有我們自己新增的一些設定,最常用的就是appSettings節點,用來設定資料庫連線和引數。
使用的話就參照包System.Configuration.ConfigurationManager 之後取裡面的設定資訊:System.Configuration.ConfigurationManager.AppSettings["ConnectionString"]

隨著技術的發展這種設定方式顯得冗餘複雜,如果設定項太多層級關係參數列達凌亂,在.net core開始也將設定的格式預設成了json格式,包括現在很多的其它設定也是支援的,比如java中常用的yaml格式,為什麼能支援這麼多讀取源和格式,其實質在於設定提供程式
目前.NET 中的設定是使用一個或多個設定提供程式執行的。 設定提供程式使用各種設定源從鍵值對讀取設定資料,這些設定程式稍後我們會看到,讀取的設定源可以是如下這些:

  • 設定檔案,appsettings.json
  • 環境變數
  • Azure Key Vault
  • Azure 應用設定
  • 命令列引數
  • 已安裝或已建立的自定義提供程式
  • 目錄檔案
  • 記憶體中的 .NET 物件
  • 第三方提供程式

二、設定初識

IConfiguration 介面是所有設定源的單個表示形式,給定一個或多個設定源,IConfiguration 型別提供設定資料的統一檢視。

上圖我們可能沒有直觀的感受,現在寫一個例子來看看

(1). 新建控制檯應用程式:
建立控制檯使用的是.net 6.0 框架,vs 2022。
安裝 Microsoft.Extensions.Configuration.Json NuGet 包,該包提供json組態檔讀取。

Install-Package Microsoft.Extensions.Configuration.Json

(2). 新增appsettings.json 檔案

{
  "person": {
    "name": "XSpringSun",
    "age": 18
  }
}

(3). 使用json提供程式讀取json設定
new一個ConfigurationBuilder,新增json設定,AddJsonFile是在包中的IConfigurationBuilder擴充套件方法,其它設定提供程式也是用這種擴充套件方法實現。

        static void Main(string[] args)
        {

            IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();

            Console.WriteLine(configuration["person:name"]);
            Console.WriteLine(configuration["person:age"]);

            Console.WriteLine("Hello, World!");
            Console.ReadLine();
        }

可以看到已經取到json組態檔中的值了,設定值可以包含分層資料。 分層物件使用設定鍵中的 : 分隔符表示。在下面的偵錯物件中我們可以看到實際configuration的Providers 提供程式陣列有一個值,就是我們的JsonConfigurationProvider,並且JsonConfigurationProvider裡面已經讀取了json的資料儲存在Data陣列中。

對於如上幾行程式碼幹了什麼呢:

  • 將 ConfigurationBuilder 範例化(new ConfigurationBuilder)。
  • 新增 "appsettings.json" 檔案,由 JSON 設定提供程式識別(AddJsonFile("appsettings.json"))。
  • 使用 configuration 範例獲取所需的設定

三、選項模式

這樣已經實現json進行設定讀取,但是取值的方式似乎和以前沒什麼太大變法,所以.net提供了選項模式,選項模式就是使用類來提供對相關設定組的強型別存取。
我們建立一個Config類用來轉換json:

namespace ConfigDemo
{
    public class Config
    {
        public Person? person { get; set; }
    }

    public class Person {
        public string? name { get; set; }
        public int age { get; set; }
    }
}

繫結設定

IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();


            Config options = new Config();
            ConfigurationBinder.Bind(configuration, options);

            Person person = configuration.GetSection("person").Get<Person>();

            Console.WriteLine(options.person.name);
            Console.WriteLine(options.person.age);

            Console.WriteLine("-----------GetSection獲取-------------");
            Console.WriteLine(person.name);
            Console.WriteLine(person.age);

用了兩種方式獲取設定,第一種使用ConfigurationBinder.Bind()將整個設定繫結到物件Config上,另外一種是使用IConfiguration的GetSection().Get<T>()並返回指定的型別。兩種方式都可以使用,看實際需求和用途。

四、選項依賴注入

在控制檯程式中我們參照DI注入包,然後演示下如何進行設定的注入。關於DI和IOC不清楚的看我上篇文章.net 溫故知新:【7】IOC控制反轉,DI依賴注入

  • 新建一個測試類TestOptionDI
    public class TestOptionDI
    {
        private readonly IOptionsSnapshot<Config> _options;
        public TestOptionDI(IOptionsSnapshot<Config> options)
        {
            _options = options;
        }

        public void Test()
        {
            Console.WriteLine("DI測試輸出:");
            Console.WriteLine($"姓名:{_options.Value.person.name}");
            Console.WriteLine($"年齡:{_options.Value.person.age}");
        }
    }

在測試類中我們使用IOptionsSnapshot<T>介面作為依賴注入,還有其它不同定義的介面用來設定注入,關於選項介面:。

不同介面可以配合讀取設定的不同方式起作用,IOptionsSnapshot介面可以在組態檔改變後不同作用域進行重新整理設定。接著我們修改main方法,引入DI,並將AddJsonFile方法的引數reloadOnChange設定為true,optional引數是否驗證檔案存在,建議開發時都設定為true,這樣如果檔案有問題會進行報錯。
注入設定這句services.AddOptions().Configure<Config>(e=>configuration.Bind(e))是關鍵,通過容器呼叫AddOptions方法註冊,然後Configure方法裡面是一個委託方法,該委託的作用就是將設定的資訊系結到Config型別的引數e上。註冊到容器的泛型選項介面,這樣在TestOptionDI類建構函式注入就能注入IOptionsSnapshot了,這裡有點繞。

        static void Main(string[] args)
        {

            IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json",optional:true,reloadOnChange:true)
                .Build();
            
            //IServiceCollection 服務
            ServiceCollection services = new ServiceCollection();
            //注入設定
            services.AddOptions().Configure<Config>(e=>configuration.Bind(e));
            //注入TestOptionDI
            services.AddScoped<TestOptionDI>();

            using (var provider = services.BuildServiceProvider())
            {
                //獲取服務
                var testOption = provider.GetRequiredService<TestOptionDI>();
                testOption.Test();
            }
            Console.ReadLine();
        }

為了測試IOptionsSnapshot介面在不同作用域會重新整理設定,我們修改下main方法,用一個while迴圈在ReadLine時修改json檔案值,不同的Scope裡進行列印。

            using (var provider = services.BuildServiceProvider())
            {
                while (true)
                {
                    using (var scope = provider.CreateScope())
                    {
                        //獲取服務
                        var testOption = scope.ServiceProvider.GetRequiredService<TestOptionDI>();
                        testOption.Test();
                    }
                    Console.ReadLine();
                }
            }

這個功能在web中使用很方便,因為框架的一次請求就是一個作用域,所以我們修改了設定,下次請求就能生效了,而不用重啟服務。

五、其它設定

如最開始所說,不僅能設定json檔案,由於各種提供程式,還可以設定其它的,但是根據設定的順序會進行覆蓋。我們只新增一個環境變數設定演示下:
首先新增提供程式包:Install-Package Microsoft.Extensions.Configuration.EnvironmentVariables
然後新增環境變數設定程式碼AddEnvironmentVariables()

IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json",optional:true,reloadOnChange:true)
                .AddEnvironmentVariables()
                .Build();

在VS中設定臨時環境變數

這裡有個扁平化設定,就是表示層級用冒號person:age

六、託管模式

對於web專案我們沒有進行這麼多操作它是怎麼設定的呢,其實框架已經自動幫我們做了,其它非web專案也可以使用這種託管模式,在Microsoft.Extensions.Hosting 包中,只需要使用簡單的程式碼就能設定好。

IHost host = Host.CreateDefaultBuilder(args).Build();
await host.RunAsync();

其載入設定的優先順序:

通過分析我們對整個設定如何執行的機制有了一個大體的瞭解,如果想詳細瞭解託管模式的還是建議看官方檔案:.NET設定