上面提到,通過 IConfigurationBuilder 的實現類物件,我們可以自由地往設定系統中新增不同的設定提供程式,從而獲取不同來源的設定資訊。.NET Core 中,微軟提供了以下這些內建的設定提供程式:
這裡稍微介紹一下常用的幾個。
顧名思義,這個就是我們熟悉的設定載入方式,從組態檔中載入設定資訊。組態檔多種多樣,.NET Core框架內建支援Json、xml、ini三種格式的檔案提供程式:
以上這些設定提供程式,均繼承於抽象基礎類別 FileConfigurationProvider,當一個提供程式中發現重複的鍵時,提供程式會引發 FormatException,所有型別的檔案提供程式都是這樣的機制。
另外,所有檔案設定提供程式都支援提供兩個設定引數:
JSON設定提供程式被封裝在 Microsoft.Extensions.Configuration.Json Nuget包中,若通過 ConfigurationBuilder 自行構建設定系統需要先安裝該依賴包。它通過 JsonConfigurationProvider 在執行時從 Json 檔案中載入設定。
使用方式非常簡單,通過 IConfigurationBuilder 的實現類物件呼叫 AddJsonFile 擴充套件方法指定Json組態檔的路徑即可。以下程式碼可用於控制檯程式中建立主機並設定設定系統:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// 清除原有的設定提供程式
config.Sources.Clear();
var env = context.HostingEnvironment;
// 新增 json 組態檔
config.AddJsonFile("appsettings.json",true, true)
.AddJsonFile($"appsetting.{env.EnvironmentName}.json", true, true);
})
.Build();
var configuration = host.Services.GetService<IConfiguration>();
Console.WriteLine($"Settings:Provider: {configuration.GetValue<string>("Settings:Provider")}");
host.Run();
appsetting.json 組態檔中的內容如下:
{
"Settings": {
"Provider": "JsonProvider",
"version": {
"subKey1": "value",
"subKey2": 1
},
"items": [ "item1", "item2", "item3" ]
}
}
控制檯程式執行之後輸出如下:
這樣有一點要注意的是,對於我們手動新增的組態檔需要設定一下檔案屬性,讓其在專案生成的時候能夠正常生成到執行目錄,確保應用可以正常獲取到該檔案:
XML設定提供程式被封裝在 Microsoft.Extensions.Configuration.Xml Nuget包中,通過 XmlConfigurationProvider 類在執行時從 XML 檔案載入設定。
使用方式也很簡單,與 JSON 設定提供程式類似,通過 AddXmlFile 擴充套件方法指定組態檔路徑。
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// 清除原有的設定提供程式
config.Sources.Clear();
var env = context.HostingEnvironment;
//// 新增 json 組態檔
//config.AddJsonFile("appsettings.json",true, true)
// .AddJsonFile($"appsetting.{env.EnvironmentName}.json", true, true);
config.AddXmlFile("appsettings.xml", true, true);
})
.Build();
var configuration = host.Services.GetService<IConfiguration>();
Console.WriteLine($"Settings:Provider: {configuration.GetValue<string>("Settings:Provider")}");
Console.WriteLine($"Settings:items[1]: {configuration.GetValue<string>("Settings:items:1")}");
host.Run();
xml組態檔內容如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<Settings>
<Provider>XmlProvider</Provider>
<version>
<subKey1>value</subKey1>
<subKey2>1</subKey2>
</version>
<items>item1</items>
<items>item2</items>
<items>item3</items>
</Settings>
</configuration>
執行程式控制臺輸出如下:
這裡有一個和版本有關的點,對Xml檔案中使用同一元素名稱的重複元素,一般也就是陣列,.NET 6及之後的xml設定提供程式會自動為其編制索引,不再需要顯式指定name屬性。如果是 .NET 6 以下的版本則需要這樣了:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<Settings>
<Provider>XmlProvider</Provider>
<version>
<subKey1>value</subKey1>
<subKey2>1</subKey2>
</version>
<items name="itemkey1">item1</items>
<items name="itemkey2">item2</items>
<items name="itemkey3">item3</items>
</Settings>
</configuration>
Console.WriteLine($"Settings:items[1]: {configuration.GetValue<string>("Settings:items:itemkey2")}");
另外xml檔案中的屬性也可用於提供值:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<key attribute="value" />
<section>
<key attribute="value" />
</section>
</configuration>
獲取屬性的值可用以下設定鍵:
key:attribute
section:key:attribute
INI 設定提供程式被封裝在 Microsoft.Extensions.Configuration.Ini Nuget包,通過 IniConfigurationProvider 類在執行時從 INI 檔案載入設定。使用方式如下:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// 清除原有的設定提供程式
config.Sources.Clear();
var env = context.HostingEnvironment;
//// 新增 json 組態檔
//config.AddJsonFile("appsettings.json",true, true)
// .AddJsonFile($"appsetting.{env.EnvironmentName}.json", true, true);
//config.AddXmlFile("appsettings.xml", true, true);
config.AddIniFile("appsettings.ini", true, true);
})
.Build();
var configuration = host.Services.GetService<IConfiguration>();
Console.WriteLine($"Settings:Provider: {configuration.GetValue<string>("Settings:Provider")}");
Console.WriteLine($"Settings:items[1]: {configuration.GetValue<string>("Settings:items:1")}");
host.Run();
ini組態檔內容如下:
[Settings]
Provider="IniProvider"
version:subKey1="value"
version:subKey2=1
items:0="item1"
items:1="item2"
items:3="item3"
執行應用,控制檯輸出如下:
環境變數設定提供程式被封裝在 Microsoft.Extensions.Configuration.EnvironmentVariables, 通過 EnvironmentVariablesConfigurationProvider 在執行時從環境變數中以鍵值對的方式載入設定。
環境變數一般情況下是設定在機器上的,而不同的作業系統對環境變數的設定要求有所不同,當環境變數存在多層的時候,層級之間的分隔有些支援通過 : 號進行分隔,有些不支援,雙下劃線()是全平臺支援的,所以設定環境變數的時候要使用雙下劃線()來代替冒號(:)。
各種不同的平臺下怎麼去新增環境變數這裡就不細說了,Windows 下大家最起碼都應該知道可以通過 我的電腦 -> 屬性 -> 高階系統設定 去視覺化的新增,命令列的方式可閱讀下官方文章: ASP.NET Core 中的設定 | Microsoft Learn,Linux 平臺下可以通過 export 命令臨時新增,或者修改相應的組態檔 ~/.bashrc 或 /etc/profile,大家仔細查一下資料就行了。
處理在機器上直接設定環境變數外,我們開發測試的過程中也可以通過 ASP.NET Core框架下的 launchSettings.json 組態檔設定用於偵錯的臨時環境變數。在應用啟動偵錯時,該檔案中的環境變數會替代系統的中的環境變數。
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"ConfigurationSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5004",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"Custom_settings__Provider": "EnvironmentVariablesProvider",
"Custom_settings__version__subKey1": "value",
"Custom_settings__items__0": "item1",
"Custom_settings__items__1": "item2",
"Custom_settings__items__2": "item3"
}
}
}
}
環境變數設定提供程式使用也很簡單,注意以下範例為了使用 launchSettings.json 中的環境變數是在 ASP.NET Core專案中測試的。
var builder = WebApplication.CreateBuilder(args);
builder.Host.ConfigureAppConfiguration(builder =>
{
builder.Sources.Clear();
// 篩選前置為 Custom_ 的環境變數,將其載入為應用設定,其他的不載入
builder.AddEnvironmentVariables("Custom_");
});
var app = builder.Build();
Console.WriteLine($"Settings:Provider: {app.Configuration.GetValue<string>("Settings:Provider")}");
Console.WriteLine($"Settings:items[1]: {app.Configuration.GetValue<string>("Settings:items:1")}");
app.Run();
在新增環境變數時,通過指定引數 prefix,唯讀取限定字首的環境變數。不過在讀取環境變數時,會將字首刪除。如果不指定引數 prefix,那麼會讀取所有環境變數。
當建立預設通用主機(Host)時,預設就已經新增了字首為 DOTNET_
的環境變數,如果是在 ASP.NET Core 中,設定了 Web主機時,預設新增了字首為 ASPNETCORE_
的環境變數,而後主機載入應用設定時,再根據策略新增了其他的環境變數,如果沒有傳遞 prefix 引數則是所有環境變數。這一塊的載入機制,下面再細講。
執行應用,控制檯輸出如下:
除此之外,環境變數提供程式還有一些隱藏的功能點,當沒有向 AddEnvironmentVariables 傳入字首時,預設也會針對含有以下字首的環境變數進行特殊處理:
這個功能點比較少用到,但是大家看到這個大概都會有點疑惑,具體的形式是怎麼樣的,下面稍微測試一下
首先在 launchSettings.json 檔案中新增多一個環境變數:
"MYSQLCONNSTR_Default": "Server=myServerAddress;Database=myDataBase;Uid=myUsername;Pwd=myPassword;"
之後在應用中列印如下兩個設定:
Console.WriteLine($"ConnectionStrings:Default: { app.Configuration.GetValue<string>("ConnectionStrings:Default") }");
Console.WriteLine($"ConnectionStrings:Default_Provider: { app.Configuration.GetValue<string>("ConnectionStrings:Default_ProviderName") }");
輸出結果如下:
也就是說,這種形式的環境變數會被自動轉換為兩個。
命令列設定提供程式被封裝在 Microsoft.Extensions.Configuration.CommandLine 包中,通過CommandLineConfigurationProvider在執行時從命令列引數鍵值對中載入設定。
當我們通過 dotnet 命令啟動一個 .NET Core 應用時,我們可以在命令後面追加一些引數,這些引數將在入口檔案中被 args 變數接收到。命令列設定提供程式使用如下:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// 清除原有的設定提供程式
config.Sources.Clear();
config.AddCommandLine(args);
})
.Build();
var configuration = host.Services.GetService<IConfiguration>();
Console.WriteLine($"Settings:Provider: {configuration.GetValue<string>("Settings:Provider")}");
Console.WriteLine($"Settings:items[1]: {configuration.GetValue<string>("Settings:items:1")}");
host.Run();
之後通過命令列程式啟動應用,並傳入相應的引數:
dotnet ConfigurationSampleConsole.dll Settings:Provider=CommandLineProvider Settings:items:1=item1
命令列引數的設定有三種方式:
(1) 使用 = 號連線鍵值:
dotnet ConfigurationSampleConsole.dll Settings:Provider=CommandLineProvider Settings:items:0=item1 Settings:items:1=item2
(2) 使用 / 號表示鍵,值跟在鍵後面,鍵值以空格分隔
dotnet ConfigurationSampleConsole.dll /Settings:Provider CommandLineProvider /Settings:items:0 item1 /Settings:items:1 item2
(3) 使用 -- 符號表示鍵,值跟在鍵後面,鍵值以空格分隔
dotnet ConfigurationSampleConsole.dll --Settings:Provider CommandLineProvider --Settings:items:0 item1 --Settings:items:1 item2
如果值之中本來就有空格的,可以使用 "" 號包括。
dotnet ConfigurationSampleConsole.dll --Settings:Provider CommandLineProvider --Settings:items:0 item1 --Settings:items:1 "test item2"
AddCommandLine 擴充套件方法提供了過載,允許額外傳入一個引數,該引數提供一個交換對映字典,針對命令列設定引數進行key對映。例如命令列傳入鍵是 name01 ,對映後的的鍵為 project:name。這裡有一些要注意的點:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// 清除原有的設定提供程式
config.Sources.Clear();
var switchMappings = new Dictionary<string, string>
{
["--b1"] = "Settings:Provider",
["-b2"] = "Settings:items"
};
config.AddCommandLine(args, switchMappings);
})
.Build();
var configuration = host.Services.GetService<IConfiguration>();
Console.WriteLine($"Settings:Provider: {configuration.GetValue<string>("Settings:Provider")}");
Console.WriteLine($"Settings:items[1]: {configuration.GetValue<string>("Settings:items:1")}");
host.Run();
3.4 記憶體設定提供程式
記憶體設定提供程式就比較簡單了,它直接被包含在 Microsoft.Extensions.Configuration,通過MemoryConfigurationProvider在執行時從記憶體中的集合中載入設定。使用方式如下:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// 清除原有的設定提供程式
config.Sources.Clear();
config.AddInMemoryCollection(new Dictionary<string, string> {
{ "Settings:Provider", "InMemoryProvider" },
{ "Settings:items:1", "MemoryItem" }
});
})
.Build();
var configuration = host.Services.GetService<IConfiguration>();
Console.WriteLine($"Settings:Provider: {configuration.GetValue<string>("Settings:Provider")}");
Console.WriteLine($"Settings:items[1]: {configuration.GetValue<string>("Settings:items:1")}");
host.Run();![image]
3.5 設定載入順序
上面介紹了一些常用的設定提供程式,這些設定提供程式都是通過擴充套件方法新增到 ConfigurationBuilder 物件中的,而從上面 ConfigurationBuilder 的原始碼可以看出,新增一個設定提供程式的時候其實應該是新增了一個對應的 IConfigurationSource 物件,而後在 ConfigurationBuilder 中被儲存到集合中。
這就可以看出,設定系統是允許同時新增多種設定提供程式,支援多來源的設定資訊同時存在的。那麼當多個設定處理程式都被新增到設定系統之中,那我們從設定系統中通過設定鍵獲取設定值的時候是怎麼進行的呢,當多個設定提供程式存在相同的設定鍵時,我們獲取到的設定值是哪個呢?
從 ConfigurationRoot 的原始碼中可以可以看到,當我們用索引器 API 讀取設定值時,是呼叫了 GetConfiguration 方法
而 GetConfiguration 方法中的邏輯也很簡單,只是遍歷提供程式集合嘗試從提供程式去獲取值,需要關注的是遍歷的順序。
這裡的邏輯是這樣子的,倒敘進行遍歷,後新增的設定處理程式先被遍歷,一旦通過key從提供程式中獲取到值就返回結果,不再繼續遍歷。所以新增設定提供程式的順序決定相同設定鍵最終的值, 當多個設定處理程式存在相同鍵時,越後新增的設定提供程式優先順序越高,從最後的一個提供程式獲取到值之後就不再從其他處理程式獲取。
上面也有提到通過主機執行和管理應用,在通過主機執行的專案中,主機在啟動的時候就已經預設新增了一些設定提供程式,所以我們建立了一個 ASP.NET Core 模板專案之後就可以獲取到 appsettings.json 等組態檔中的設定資訊。下面介紹一下預設新增的設定提供程式。
在 Host.CreateDefaultBuilder(String[]) 方法或者 WebApplication.CreateBuilder(args) 方法執行的時候,會按照以下順序新增應用的設定提供程式:
(1) 記憶體設定提供程式
(2) Chained 設定提供程式(新增現有的主機設定)
(3) JSON 設定提供程式 (新增 appsettings.json 組態檔)
(4) JSON 設定提供程式 (新增 appsettings.{Environment}.json 組態檔)
(5) 機密管理器(僅Windows)
(6) 環境變數設定提供程式 (未限定字首)
(7) 命令列設定提供程式
設定分主機設定和應用設定,主機啟動時應用仍未啟動,主機啟動過程中的設定就是主機設定。上面第一個Chained 設定提供程式就是承接過來的主機設定。而主機設定是按照以下順序載入的:
(1) 環境變數設定提供程式(以 DOTNET_ 為字首的環境變數)
(2) 命令列設定提供程式 (命令列引數)
(3) 環境變數設定提供程式(以 ASPNETCORE_ 為字首的環境變數,如果是Web主機的話)
所以最終的應用設定載入順序應該是下面這樣:
(1) 記憶體設定提供程式
(2) 環境變數設定提供程式(以 DOTNET_ 為字首的環境變數)
(3) 命令列設定提供程式 (命令列引數)
(4) 環境變數設定提供程式(以 ASPNETCORE_ 為字首的環境變數,如果是Web主機的話)
(5) JSON 設定提供程式 (新增 appsettings.json 組態檔)
(6) JSON 設定提供程式 (新增 appsettings.{Environment}.json 組態檔)
(7) 機密管理器(僅Windows)
(8) 環境變數設定提供程式 (未限定字首)
(9) 命令列設定提供程式 (命令列引數)
按照越後面新增的提供程式優先的方式,最終應用設定會覆蓋主機設定,並且最優先是最後新增的命令列設定提供程式,我們可以通過以下方式列印設定系統中所有的設定提供程式,進行驗證:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var configurationRoot = (IConfigurationRoot)app.Configuration;
foreach (var provider in configurationRoot.Providers.AsEnumerable())
{
Console.WriteLine(provider.ToString());
}
app.Run();
最終控制檯列印出來的結果如下:
最終控制檯列印出來的結果如下:
雖然應用設定優先,會覆蓋前面的主機設定,但是有一些變數會在初始化主機生成器的時候就提前進行鎖定,並且之後不會受應用設定的影響:
這裡提到環境名稱,其實也就是軟體執行的環境,最最基本的也會分為開發環境、生產環境兩種。軟體執行環境通過環境變數來設定,普通的.NET Core 應用環境變數key為NETCORE_ENVIRONMENT,Web應用環境變數key為ASPNETCORE_ENVIRONMENT,Web應用下如果兩者同時存在,後者會覆蓋前者。軟體應用根據不同的環境會有不同的行為邏輯,例如上面講到的 appsettings.{environment}.json 根據環境而不同的組態檔,例如之前的 入口檔案 文章中講到的 Startup 檔案根據不同環境的分離設定方式,而我們在程式碼中有時也會根據環境處理不同的邏輯,這時候我們可以注入 IHostEnvironment 服務,通過它獲取當前應用的執行環境,入口檔案中無論是 WebApplicationBuilder 物件還是 WebApplication 物件都包含該型別的屬性。
通過環境變數設定當前執行環境,其實環境變數的值只是一個字串,我們可以設定成任意值,這是執行的,.NET Core 框架下 IHostEnvironment 也能夠正常載入到相應的環境名稱,但是.NET Core 預設只提供了對 Development、Production 和 Staging 三種環境的判別,以及相應的處理邏輯和擴充套件方法,如果是其他的自定義環境則需要開發人員自行進行相應的處理了。和 .NET Core 應用環境相關的知識點大家可以看一下官方檔案: 在 ASP.NET Core 中使用多個環境 | Microsoft Learn
除了上面講到的主機設定,其他還有一些主機設定,例如URLS,但這個是可以通過應用設定設定的,讀取相應的設定值時也應用從應用設定讀取。
URLS 設定Web應用啟動後的存取地址,這個設定可以多個地方設定,其中命令列引數最優先,其他地方設定的應該被命令列引數覆蓋。但是如果通過Kestrel 終結點方式設定了Web應用的存取地址,那Kestrel 終結點的設定將覆蓋其他所有的存取地址的設定。
如在 appsettings.json 中新增以下設定:
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://localhost:9999"
}
}
}
那麼以下幾種方式設定的 URLS 都會失效:
也就是說,就算我們用以下命令啟動應用,應用最終的存取地址還是以Kestrel終結點設定的為準:
dotnet run --urls="https://localhost:7777"
Kestrel 設定與 URLS 設定不是一個引數,我們可以通過在命令列或者環境變數中設定 kestrel 中間點設定來覆蓋 appsettings.json 中的,這又回到設定提供程式的優先順序問題了。
set Kestrel__Endpoints__Https__Url=https://localhost:8888
dotnet run Kestrel__Endpoints__Https__Url=https://localhost:8888
在主機啟動的邏輯中Kestrel具備更高的最終優先順序,但是其實主機內部是先根據URLS建立了一個終結點,之後又替換為 Kestrel 設定的終結點的。通過應用啟動時的控制檯輸出可以看出。
這種情況對於單機應用沒有什麼影響,但是對於使用自動服務發現的微服務架構而言就可能有問題了,可能導致註冊服務註冊中心的終結點是第一個,而後應用終結點又被改變,導致註冊中心記錄的服務終結點和實際的不一致。
這一篇的內容比較多,但是不大好拆分,整個知識點是一個整體,通過這一章的內容相信大家能夠對 .NET Core 框架設定系統內部的工作原理有一個詳細的瞭解。
參考文章:
ASP.NET Core 中的設定 | Microsoft Learn
設定 - .NET | Microsoft Learn
理解ASP.NET Core - 設定(Configuration)
ASP.NET Core 系列:
目錄:ASP.NET Core 系列總結
上一篇:ASP.NET Core - 設定系統之設定新增