一個(gè)應(yīng)用要運(yùn)行起來,往往需要讀取很多的預(yù)設(shè)好的配置信息,根據(jù)約定好的信息或方式執(zhí)行一定的行為。
配置的本質(zhì)就是軟件運(yùn)行的參數(shù),在一個(gè)軟件實(shí)現(xiàn)中需要的參數(shù)非常多,如果我們以 Hard Code (硬編碼)的方式寫在應(yīng)用代碼中,這樣配置就會(huì)很亂,而且后續(xù)也不容易修改。亂而多,而且不容易修改,這就需要一個(gè)統(tǒng)一管理的地方,最常見的方式就是配置文件,這個(gè)也是開發(fā)人員非常熟悉的方式。
通過配置文件設(shè)置好軟件應(yīng)用運(yùn)行的各種參數(shù)之后,我們?cè)陂_發(fā)過程中需要能夠讀取到配置文件的內(nèi)容,根據(jù)配置內(nèi)容進(jìn)行軟件邏輯的判斷,實(shí)現(xiàn)完善的軟件行為邏輯。這一篇就是介紹 .NET Core 框架下怎么使用配置系統(tǒng),這也是 .NET Core 下的基礎(chǔ)設(shè)施之一。
1. 配置讀取
配置讀取是配置系統(tǒng)最基本的操作,幾乎是每個(gè)開發(fā)人員都會(huì)進(jìn)行的操作,一個(gè)開發(fā)人員可能不清楚配置系統(tǒng)是怎么實(shí)現(xiàn)的,配置文件是怎么解析的,但一定都做過讀取配置信息的操作。.NET Core 框架下對(duì)于配置系統(tǒng)的使用最終暴露出來的接口是 IConfiguration
,它是供配置數(shù)據(jù)的統(tǒng)一視圖,配置讀取就通過這個(gè)接口的實(shí)現(xiàn)來進(jìn)行。
默認(rèn)創(chuàng)建的 ASP .NET Core 框架模板項(xiàng)目中默認(rèn)有一個(gè)appsettings.json
配置文件,這個(gè)也是 ASP.NET Core 中最常用的配置文件,在配置文件中添加多一個(gè) Settings 節(jié)點(diǎn),內(nèi)容如下:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Settings": {
"key1": "value1",
"key2": 1,
"key3": true,
"key4": {
"subKey1": "value",
"subKey2": 1
},
"items": [ "item1", "item2", "item3" ]
}
}
我們要讀取配置文件中的內(nèi)容,例如讀取 “AllowedHosts” 對(duì)于的值,只需要將其注入到需要的服務(wù)類中即可使用,ASP.NET Core 模板項(xiàng)目中使用 Web 主機(jī)構(gòu)建和管理應(yīng)用,在使用主機(jī)默認(rèn)配置的時(shí)候已經(jīng)將 IConfiguration
服務(wù)注冊(cè)到依賴注入容器之中。
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly IConfiguration _configuration;
public WeatherForecastController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpGet]
public Task Get()
{
var allowedHosts = _configuration["AllowedHosts"];
Console.WriteLine(allowedHosts);
// 配置鍵不區(qū)分大小寫
var allowedHosts = _configuration["AllowedHosts"];
Console.WriteLine(allowedHosts);
return Task.CompletedTask;
}
}
上面這種讀取方式是索引器方式,最簡(jiǎn)單也是基本的方式,配置被加載到內(nèi)存中是以鍵值對(duì)的方式存在的,我們可以通過配置鍵讀取配置值,鍵是字符串,不區(qū)分大小寫,讀取出來的值都是字符串。
配置文件中配置值往往不止一層,就像上面 appsettings.json
文件中,Logging 節(jié)點(diǎn)下還有子節(jié)點(diǎn),如果需要這種分層數(shù)據(jù),可以使用 : 字符(英文冒號(hào))分隔層次結(jié)構(gòu),例如獲取上面配置鍵 Default 對(duì)于的配置值。
// 以 : 作為分隔符,表示層級(jí)結(jié)構(gòu)
var defalutLogLevel = _configuration["Logging:LogLevel:Default"];
如果配置值是數(shù)組,需要讀取數(shù)組中具體的某一個(gè)值,可以用該值在數(shù)組中的索引作為 key,例如讀取上面配置文件中的 items 數(shù)組中的 item2。
// 讀取數(shù)組,可以用值在數(shù)組中的索引作為key
var item2 = _configuration["Settings:items:1"];
這種方式讀取配置有挺多不方便的地方,例如配置值是數(shù)值型的時(shí)候,需要我們直接轉(zhuǎn)換,例如一次只能讀取到一個(gè)配置值。微軟通過 Microsoft.Extensions.Configuration.Binder
中的 ConfigurationBinder
類提供了一些 IConfiguration
的靜態(tài)方法,用于獲取配置值時(shí)進(jìn)行自動(dòng)轉(zhuǎn)換和綁定。
(1) GetValue
通過 ConfigurationBinder 中的 GetValue
擴(kuò)展方法,一樣可以通過配置鍵從配置系統(tǒng)中讀取對(duì)于的配置值。該方法有多個(gè)重載,支持通過泛型的方式進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換,并且支持設(shè)置默認(rèn)值。
var defaultLogLevel2 = _configuration.GetValue<string>("Logging:LogLevel:Default");
// 配置信息中不包含 "Logging:LogLevel:Default" 這個(gè)Key時(shí),以默認(rèn)值 "Error" 返回
var defaultLogLevel3 = _configuration.GetValue<string>("Logging:LogLevel:Default", "Error");
(2) GetSection
這樣子有些情況下仍然無法滿足我們的需要,某一些情況下我們會(huì)需要直接讀取配置中的一部分節(jié)點(diǎn),例如直接讀取上面配置中的 LogLevel 部分。IConfiguration 中的 GetSection 方法可以通過 Key 直接讀取某一個(gè)子節(jié)點(diǎn)。該方法的返回值是 IConfigurationSection
類型,永遠(yuǎn)不會(huì)返回null,IConfigurationSection 實(shí)際上是一個(gè) IConfiguration
的派生接口,也就是說我們還可以從 IConfigurationSection 再去獲取我們需要的具體的配置值。
var section = _configuration.GetSection("Settings:key4");
var defaultLogLevel4 = section["Default"];
(3) Get
上面講到通過 GetSection 獲取到了配置文件中的一部分子節(jié)點(diǎn),但是那樣仍然不方便,還是需要一個(gè)一個(gè)去讀取具體的值??梢酝ㄟ^ ConfigurationBinder.Get 擴(kuò)展方法,將配置以強(qiáng)類型的方式綁定到對(duì)象上。
首先需要定義一個(gè)類來接收配置文件中的節(jié)點(diǎn)信息
public class KeyOptions
{
public string subKey1 { get; set; }
public int subKey2 { get; set; }
}
然后通過以下方式進(jìn)行綁定:
var keyOption1 = _configuration.GetSection("Settings:key4").Get<KeyOptions>();
(4) Bind
ConfigurationBinder.Bind 擴(kuò)展方法與 Get 方法類似,也是用于將配置綁定為強(qiáng)類型對(duì)象,不過 Bind的 方法是綁定到一個(gè)已實(shí)例化的對(duì)象上,需要提供一個(gè)已存在的對(duì)象。
var keyOption2 = new KeyOptions();
_configuration.GetSection("Settings:key4").Bind(keyOption2);
(5) Exists
上面說過,GetSection 方法獲取配置中的子節(jié)點(diǎn),返回值永遠(yuǎn)不會(huì)為 null。如果我們傳入了一個(gè)不存在的 key,肯定是獲取不到對(duì)于的值的,這種情況下還是需要判斷對(duì)于的子節(jié)點(diǎn)到底是不是真正存在的,這時(shí)候可以使用 Exists 方法。
var section = _configuration.GetSection("settings");
var exist = section.Exists();
除此之外,還有一個(gè) GetChildren 方法,無需參數(shù),用于獲取到當(dāng)前配置節(jié)點(diǎn)的所有直接子節(jié)點(diǎn)的集合。
以上就是 .NET Core 體系下配置系統(tǒng)讀取配置的基本介紹,涉及到的類型最主要的是 IConfiguration 接口,除此之外還有上面提到的 IConfigurationSection 接口,以及 IConfigurationRoot 接口。
IConfigurationRoot 表示配置的根節(jié)點(diǎn),是 IConfiguration 的派生接口,以下為接口的定義:
public interface IConfigurationRoot: IConfiguration{
// 存放了當(dāng)前應(yīng)用程序的所有配置提供程序
IEnumerable<IConfigurationProvider> Providers { get; }
// 強(qiáng)制從配置提供程序中重載配置
void Reload();
}
這里可以看到一個(gè)關(guān)鍵的屬性 IEnumerable<IConfigurationProvider> Providers
,這個(gè)就是配置系統(tǒng)中的配置信息的來源,后面會(huì)仔細(xì)講這個(gè)。而 Reload 方法中最關(guān)鍵的也是調(diào)用集合中各個(gè) IConfigurationProvider 進(jìn)行數(shù)據(jù)加載。
IConfigurationSection 表示配置中的子節(jié)點(diǎn),也是 IConfiguration 的派生接口,以下為接口的定義:
public interface IConfigurationSection: IConfiguration{
// 該子節(jié)點(diǎn)在其父節(jié)點(diǎn)中所表示的 key,即直接對(duì)應(yīng)的key
string Key { get; }
// 該子節(jié)點(diǎn)在配置中的全路徑(從根節(jié)點(diǎn)開始,到當(dāng)前節(jié)點(diǎn)以:符號(hào)分隔的路徑)
string Path { get; }
// 該子節(jié)點(diǎn)的 value。如果該子節(jié)點(diǎn)是葉子節(jié)點(diǎn),則Value為該節(jié)點(diǎn)對(duì)于的值,若其下存在子節(jié)點(diǎn),則其始終為 null
string Value { get; set; }
}
IConfigurationSection 接口通過以上三個(gè)屬性,結(jié)合IConfiguration中的 GetChildren 方法來完整地表示的一個(gè)子節(jié)點(diǎn),而 Exists 方法判斷節(jié)點(diǎn)是否為空,就是針對(duì) IConfigurationSection 中的Value屬性和 GetChildren 方法來進(jìn)行的。
public static class ConfigurationExtensions{
public static bool Exists(thisIConfigurationSection section){
if(section == null)
{
returnfalse;
}
returnsection.Value != null || section.GetChildren().Any();
}
}
2. 配置添加
配置系統(tǒng)可以讀取到配置文件中的信息,那必然有某個(gè)地方可以將配置文件添加到配置系統(tǒng)中。之前的文章中講到ASP.NET Core 入口文件中,builder(WebApplicationBuilder 對(duì)象) 中有一個(gè) Configuration 屬性,這里就是我們擴(kuò)展添加額外的配置的地方。
查看 Configuration 屬性,可以看到是 ConfigurationManager 類型,而 ConfigurationManager 實(shí)現(xiàn)了 IConfigurationBuilder 接口。
IConfigurationBuilder 接口定義如下:
public interface IConfigurationBuilder{
// 存放用于該 Builder 的 Sources 列表中各個(gè)元素的共享字典
IDictionary<string, object> Properties { get; }
// 已注冊(cè)的 IConfigurationSource 列表
IList<IConfigurationSource> Sources { get; }
// 將 IConfigurationSource 添加到 Sources 中
IConfigurationBuilder Add(IConfigurationSource source);
// 通過 Sources 構(gòu)建配置提供程序?qū)嵗?,并?chuàng)建 IConfigurationRoot 實(shí)例
IConfigurationRoot Build();
}
而它的實(shí)現(xiàn)類 ConfigurationBuilder 就是配置系統(tǒng)的入口。
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(nameof(source));
}
Sources.Add(source);
return this;
}
public IConfigurationRoot Build()
{
var providers = new List<IConfigurationProvider>();
foreach (IConfigurationSource source in Sources)
{
IConfigurationProvider provider = source.Build(this);
providers.Add(provider);
}
return new ConfigurationRoot(providers);
}
}
當(dāng)我們啟動(dòng)一個(gè) ASP.NET Core 應(yīng)用的時(shí)候是創(chuàng)建并啟動(dòng)了一個(gè) Web 主機(jī),由 Web 主機(jī)來啟動(dòng)并管理我們的應(yīng)用的生命周期,在這個(gè)過程中會(huì)默認(rèn)添加一些配置提供程序,加載一些配置信息。這些操作就在以下代碼中:
var builder = WebApplication.CreateBuilder(args);
.NET Core 框架下的主機(jī)除了適用于 Web 應(yīng)用的 Web 主機(jī)之外,還有通用主機(jī)。若是在普通的控制臺(tái)應(yīng)用程序,想要通過主機(jī)啟動(dòng)應(yīng)用,并使用配置系統(tǒng)可用以下方式:
(1) 添加 Microsoft.Extensions.Hosting Nuget 包
(2) 通過以下代碼創(chuàng)建主機(jī)
using IHost host = Host.CreateDefaultBuilder(args).Build();
host.Run();
主機(jī)這塊就先稍微了解以下,后面還會(huì)有專門的文章去介紹。除了通過主機(jī)的方式使用配置系統(tǒng)之外,我們也可以直接通過 ConfigurationBuilder 類構(gòu)建,如下:
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
config.GetValue<string>("Logging:LogLevel:Default");
這里需要引入相應(yīng)的 Nuget 包,例如使用 ConfigurationBinder 相關(guān)的擴(kuò)展方法就要引入 Microsoft.Extensions.Configuration.Binder Nuget包,使用 Json 格式配置文件就要引入Microsoft.Extensions.Configuration.Json Nuget包,該Nuget 包中包含 AddJsonFile 擴(kuò)展方法,通過指定文件路徑添加相應(yīng)的配置文件到配置系統(tǒng)中。
配置文件多種多樣,比較常用的都有 Json 、xml、ini、yaml 等多種,甚至配置信息不一定存儲(chǔ)在文件之中。.NET Core 配置系統(tǒng)對(duì)各種來源的配置信息進(jìn)行了抽象,不同來源只要提供相應(yīng)的配置提供程序即可,也就是我們上面在 IConfigurationRoot 接口中看到的 IConfigurationProvider 接口的實(shí)現(xiàn)類。配置提供程序內(nèi)部對(duì)不同來源不同格式的配置信息進(jìn)行加載、刷新,并提供統(tǒng)一的訪問方式,也就是鍵值對(duì),實(shí)際上所有的配置信息最終會(huì)以鍵值對(duì)的方式被讀取到內(nèi)存中的 Dictionary 對(duì)象中。
我們要添加不同類型不同來源的配置信息,只需要通過 IConfigurationBuilder 實(shí)現(xiàn)類對(duì)象添加不同的配置提供程序即可。在 ASP.NET Core 應(yīng)用之中,可以通過以下方式進(jìn)行添加:
// 添加一個(gè)xml配置文件,并加入到配置系統(tǒng)中
var configirationFilePath = Path.Combine(Directory.GetCurrentDirectory(), "xxx.xml");
builder.Configuration.AddXmlFile(configirationFilePath);
也可以通過以下方式:
builder.WebHost.ConfigureAppConfiguration(builder =>
{
builder.AddXmlFile("");
});
這兩種方式是一樣的效果的,只不過具體的實(shí)現(xiàn)類不同,ConfigureAppConfiguration 可以將原有的配置提供程序情況,而 builder.Configuration 則不行,只能往集合后面繼續(xù)添加。
3. 配置提供程序
上面提到,通過 IConfigurationBuilder 的實(shí)現(xiàn)類對(duì)象,我們可以自由地往配置系統(tǒng)中添加不同的配置提供程序,從而獲取不同來源的配置信息。.NET Core 中,微軟提供了以下這些內(nèi)置的配置提供程序:
○ 文件配置提供程序
○ 環(huán)境變量配置提供程序
○ 命令行配置提供程序
○ Azure應(yīng)用配置提供程序
○ Azure Key Vault 配置提供程序
○ Key-per-file配置提供程序
○ 內(nèi)存配置提供程序
○ 應(yīng)用機(jī)密(機(jī)密管理器)
○ 自定義配置提供程序
這里稍微介紹一下常用的幾個(gè)。
3.1 文件配置提供程序
顧名思義,這個(gè)就是我們熟悉的配置加載方式,從配置文件中加載配置信息。配置文件多種多樣,.NET Core 框架內(nèi)置支持 Json、xml、ini 三種格式的文件提供程序:
- JSON配置提供程序(JsonConfigurationProvider)
- XML配置提供程序(XmlConfigurationProvider)
- INI配置提供程序(IniConfigurationProvider)
以上這些配置提供程序,均繼承于抽象基類 FileConfigurationProvider,當(dāng)一個(gè)提供程序中發(fā)現(xiàn)重復(fù)的鍵時(shí),提供程序會(huì)引發(fā) FormatException,所有類型的文件提供程序都是這樣的機(jī)制。
另外,所有文件配置提供程序都支持提供兩個(gè)配置參數(shù):
- optional:bool 類型,指示該文件是否是可選的。如果該參數(shù)為false,但是指定的文件又不存在,則會(huì)報(bào)錯(cuò)。
- reloadOnChange:bool 類型,指示該文件發(fā)生更改時(shí),是否要重新加載配置。
3.1.1 JSON配置提供程序
JSON 配置提供程序被封裝在 Microsoft.Extensions.Configuration.Json Nuget包中,若通過 ConfigurationBuilder 自行構(gòu)建配置系統(tǒng)需要先安裝該依賴包。它通過 JsonConfigurationProvider 在運(yùn)行時(shí)從 Json 文件中加載配置。
使用方式非常簡(jiǎn)單,通過 IConfigurationBuilder 的實(shí)現(xiàn)類對(duì)象調(diào)用 AddJsonFile 擴(kuò)展方法指定Json配置文件的路徑即可。以下代碼可用于控制臺(tái)程序中創(chuàng)建主機(jī)并設(shè)置配置系統(tǒng):
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 配置文件中的內(nèi)容如下:
{
"Settings": {
"Provider": "JsonProvider",
"version": {
"subKey1": "value",
"subKey2": 1
},
"items": [ "item1", "item2", "item3" ]
}
}
控制臺(tái)程序運(yùn)行之后輸出如下:
這樣有一點(diǎn)要注意的是,對(duì)于我們手動(dòng)添加的配置文件需要設(shè)置一下文件屬性,讓其在項(xiàng)目生成的時(shí)候能夠正常生成到運(yùn)行目錄,確保應(yīng)用可以正常獲取到該文件:
3.1.2 XML配置提供程序
XML 配置提供程序被封裝在 Microsoft.Extensions.Configuration.Xml Nuget包中,通過 XmlConfigurationProvider 類在運(yùn)行時(shí)從 XML 文件加載配置。
使用方式也很簡(jiǎn)單,與 JSON 配置提供程序類似,通過 AddXmlFile 擴(kuò)展方法指定配置文件路徑。
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 配置文件內(nèi)容如下:
<?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>
運(yùn)行程序控制臺(tái)輸出如下:
這里有一個(gè)和版本有關(guān)的點(diǎn),對(duì) Xml 文件中使用同一元素名稱的重復(fù)元素,一般也就是數(shù)組,.NET 6及之后的xml 配置提供程序會(huì)自動(dòng)為其編制索引,不再需要顯式指定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
3.1.3 INI配置提供程序
INI 配置提供程序被封裝在 Microsoft.Extensions.Configuration.Ini Nuget包,通過 IniConfigurationProvider 類在運(yùn)行時(shí)從 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();
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 配置文件內(nèi)容如下:
[Settings]
Provider="IniProvider"
version:subKey1="value"
version:subKey2=1
items:0="item1"
items:1="item2"
items:3="item3"
運(yùn)行應(yīng)用,控制臺(tái)輸出如下:
3.2 環(huán)境變量配置提供程序
環(huán)境變量配置提供程序被封裝在 Microsoft.Extensions.Configuration.EnvironmentVariables, 通過 EnvironmentVariablesConfigurationProvider 在運(yùn)行時(shí)從環(huán)境變量中以鍵值對(duì)的方式加載配置。
環(huán)境變量一般情況下是配置在機(jī)器上的,而不同的操作系統(tǒng)對(duì)環(huán)境變量的設(shè)置要求有所不同,當(dāng)環(huán)境變量存在多層的時(shí)候,層級(jí)之間的分隔有些支持通過 : 號(hào)進(jìn)行分隔,有些不支持,雙下劃線 __
是全平臺(tái)支持的,所以設(shè)置環(huán)境變量的時(shí)候要使用雙下劃線 __
來代替冒號(hào) :
。
各種不同的平臺(tái)下怎么去添加環(huán)境變量這里就不細(xì)說了,Windows 下大家最起碼都應(yīng)該知道可以通過 我的電腦 -> 屬性 -> 高級(jí)系統(tǒng)設(shè)置
去可視化的添加,命令行的方式可閱讀下官方文章: ASP.NET Core 中的配置 | Microsoft Learn,Linux 平臺(tái)下可以通過 export
命令臨時(shí)添加,或者修改相應(yīng)的配置文件 ~/.bashrc
或 /etc/profile
,大家仔細(xì)查一下資料就行了。
處理在機(jī)器上直接設(shè)置環(huán)境變量外,我們開發(fā)測(cè)試的過程中也可以通過 ASP.NET Core 框架下的 launchSettings.json 配置文件設(shè)置用于調(diào)試的臨時(shí)環(huán)境變量。在應(yīng)用啟動(dòng)調(diào)試時(shí),該文件中的環(huán)境變量會(huì)替代系統(tǒng)的中的環(huán)境變量。
{
"$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"
}
}
}
}
環(huán)境變量配置提供程序使用也很簡(jiǎn)單,注意以下示例為了使用 launchSettings.json 中的環(huán)境變量是在 ASP.NET Core 項(xiàng)目中測(cè)試的。
var builder = WebApplication.CreateBuilder(args);
builder.Host.ConfigureAppConfiguration(builder =>
{
builder.Sources.Clear();
// 篩選前置為 Custom_ 的環(huán)境變量,將其加載為應(yīng)用配置,其他的不加載
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();
在添加環(huán)境變量時(shí),通過指定參數(shù) prefix,只讀取限定前綴的環(huán)境變量。不過在讀取環(huán)境變量時(shí),會(huì)將前綴刪除。如果不指定參數(shù) prefix,那么會(huì)讀取所有環(huán)境變量。
當(dāng)創(chuàng)建默認(rèn)通用主機(jī)(Host)時(shí),默認(rèn)就已經(jīng)添加了前綴為DOTNET_的環(huán)境變量,如果是在 ASP.NET Core 中,配置了 Web 主機(jī)時(shí),默認(rèn)添加了前綴為 ASPNETCORE_
的環(huán)境變量,而后主機(jī)加載應(yīng)用配置時(shí),再根據(jù)策略添加了其他的環(huán)境變量,如果沒有傳遞 prefix 參數(shù)則是所有環(huán)境變量。這一塊的加載機(jī)制,下面再細(xì)講。
運(yùn)行應(yīng)用,控制臺(tái)輸出如下:
除此之外,環(huán)境變量提供程序還有一些隱藏的功能點(diǎn),當(dāng)沒有向 AddEnvironmentVariables 傳入前綴時(shí),默認(rèn)也會(huì)針對(duì)含有以下前綴的環(huán)境變量進(jìn)行特殊處理:
這個(gè)功能點(diǎn)比較少用到,但是大家看到這個(gè)大概都會(huì)有點(diǎn)疑惑,具體的形式是怎么樣的,下面稍微測(cè)試一下
首先在 launchSettings.json 文件中添加多一個(gè)環(huán)境變量:
"MYSQLCONNSTR_Default": "Server=myServerAddress;Database=myDataBase;Uid=myUsername;Pwd=myPassword;"
之后在應(yīng)用中打印如下兩個(gè)配置:
Console.WriteLine($"ConnectionStrings:Default: { app.Configuration.GetValue<string>("ConnectionStrings:Default") }");
Console.WriteLine($"ConnectionStrings:Default_Provider: { app.Configuration.GetValue<string>("ConnectionStrings:Default_ProviderName") }");
輸出結(jié)果如下:
也就是說,這種形式的環(huán)境變量會(huì)被自動(dòng)轉(zhuǎn)換為兩個(gè)。
3.3 命令行配置提供程序
命令行配置提供程序被封裝在 Microsoft.Extensions.Configuration.CommandLine 包中,通過 CommandLineConfigurationProvider 在運(yùn)行時(shí)從命令行參數(shù)鍵值對(duì)中加載配置。
當(dāng)我們通過 dotnet 命令啟動(dòng)一個(gè) .NET Core 應(yīng)用時(shí),我們可以在命令后面追加一些參數(shù),這些參數(shù)將在入口文件中被 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();
之后通過命令行程序啟動(dòng)應(yīng)用,并傳入相應(yīng)的參數(shù):
dotnet ConfigurationSampleConsole.dll Settings:Provider=CommandLineProvider Settings:items:1=item1
命令行參數(shù)的設(shè)置有三種方式:
(1) 使用 = 號(hào)連接鍵值:
dotnet ConfigurationSampleConsole.dll Settings:Provider=CommandLineProvider Settings:items:0=item1 Settings:items:1=item2
(2) 使用 / 號(hào)表示鍵,值跟在鍵后面,鍵值以空格分隔
dotnet ConfigurationSampleConsole.dll /Settings:Provider CommandLineProvider /Settings:items:0 item1 /Settings:items:1 item2
(3) 使用 – 符號(hào)表示鍵,值跟在鍵后面,鍵值以空格分隔
dotnet ConfigurationSampleConsole.dll --Settings:Provider CommandLineProvider --Settings:items:0 item1 --Settings:items:1 item2
如果值之中本來就有空格的,可以使用 “” 號(hào)包括。
dotnet ConfigurationSampleConsole.dll --Settings:Provider CommandLineProvider --Settings:items:0 item1 --Settings:items:1 "test item2"
AddCommandLine 擴(kuò)展方法提供了重載,允許額外傳入一個(gè)參數(shù),該參數(shù)提供一個(gè)交換映射字典,針對(duì)命令行配置參數(shù)進(jìn)行key映射。例如命令行傳入鍵是 name01 ,映射后的的鍵為 project:name。這里有一些要注意的點(diǎn):
- 交換映射key必須以
-
或--
開頭。當(dāng)使用-
開頭時(shí),命令行參數(shù)書寫時(shí)也要以-
開頭,當(dāng)使用--
開頭時(shí),命令行參數(shù)書寫時(shí)可以以--
或/
開頭。 - 交換映射字典中的 key 不區(qū)分大小寫,不能包含重復(fù) key。如不能同時(shí)出現(xiàn)
-n
和-N
,但可以同時(shí)出現(xiàn)-n
和--n
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 內(nèi)存配置提供程序
內(nèi)存配置提供程序就比較簡(jiǎn)單了,它直接被包含在 Microsoft.Extensions.Configuration,通過 MemoryConfigurationProvider 在運(yùn)行時(shí)從內(nèi)存中的集合中加載配置。使用方式如下:
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();
3.5 配置加載順序
上面介紹了一些常用的配置提供程序,這些配置提供程序都是通過擴(kuò)展方法添加到 ConfigurationBuilder 對(duì)象中的,而從上面 ConfigurationBuilder 的源碼可以看出,添加一個(gè)配置提供程序的時(shí)候其實(shí)應(yīng)該是添加了一個(gè)對(duì)應(yīng)的 IConfigurationSource 對(duì)象,而后在 ConfigurationBuilder 中被保存到集合中。
這就可以看出,配置系統(tǒng)是允許同時(shí)添加多種配置提供程序,支持多來源的配置信息同時(shí)存在的。那么當(dāng)多個(gè)配置處理程序都被添加到配置系統(tǒng)之中,那我們從配置系統(tǒng)中通過配置鍵獲取配置值的時(shí)候是怎么進(jìn)行的呢,當(dāng)多個(gè)配置提供程序存在相同的配置鍵時(shí),我們獲取到的配置值是哪個(gè)呢?
從 ConfigurationRoot 的源碼中可以可以看到,當(dāng)我們用索引器API讀取配置值時(shí),是調(diào)用了 GetConfiguration 方法
而GetConfiguration方法中的邏輯也很簡(jiǎn)單,只是遍歷提供程序集合嘗試從提供程序去獲取值,需要關(guān)注的是遍歷的順序。
這里的邏輯是這樣子的,倒敘進(jìn)行遍歷,后添加的配置處理程序先被遍歷,一旦通過key從提供程序中獲取到值就返回結(jié)果,不再繼續(xù)遍歷。所以添加配置提供程序的順序決定相同配置鍵最終的值, 當(dāng)多個(gè)配置處理程序存在相同鍵時(shí),越后添加的配置提供程序優(yōu)先級(jí)越高,從最后的一個(gè)提供程序獲取到值之后就不再從其他處理程序獲取。
3.6 默認(rèn)配置來源
上面也有提到通過主機(jī)運(yùn)行和管理應(yīng)用,在通過主機(jī)運(yùn)行的項(xiàng)目中,主機(jī)在啟動(dòng)的時(shí)候就已經(jīng)默認(rèn)添加了一些配置提供程序,所以我們創(chuàng)建了一個(gè) ASP.NET Core 模板項(xiàng)目之后就可以獲取到 appsettings.json 等配置文件中的配置信息。下面介紹一下默認(rèn)添加的配置提供程序。
在 Host.CreateDefaultBuilder(String[]) 方法或者 WebApplication.CreateBuilder(args) 方法執(zhí)行的時(shí)候,會(huì)按照以下順序添加應(yīng)用的配置提供程序:
(1) 內(nèi)存配置提供程序
(2) Chained 配置提供程序(添加現(xiàn)有的主機(jī)配置)
(3) JSON 配置提供程序 (添加 appsettings.json 配置文件)
(4) JSON 配置提供程序 (添加 appsettings.{Environment}.json 配置文件)
(5) 機(jī)密管理器(僅Windows)
(6) 環(huán)境變量配置提供程序 (未限定前綴)
(7) 命令行配置提供程序
配置分主機(jī)配置和應(yīng)用配置,主機(jī)啟動(dòng)時(shí)應(yīng)用仍未啟動(dòng),主機(jī)啟動(dòng)過程中的配置就是主機(jī)配置。上面第一個(gè)Chained 配置提供程序就是承接過來的主機(jī)配置。而主機(jī)配置是按照以下順序加載的:
(1) 環(huán)境變量配置提供程序(以 DOTNET_ 為前綴的環(huán)境變量)
(2) 命令行配置提供程序 (命令行參數(shù))
(3) 環(huán)境變量配置提供程序(以 ASPNETCORE_ 為前綴的環(huán)境變量,如果是Web主機(jī)的話)
所以最終的應(yīng)用配置加載順序應(yīng)該是下面這樣:
(1) 內(nèi)存配置提供程序
(2) 環(huán)境變量配置提供程序(以 DOTNET_ 為前綴的環(huán)境變量)
(3) 命令行配置提供程序 (命令行參數(shù))
(4) 環(huán)境變量配置提供程序(以 ASPNETCORE_ 為前綴的環(huán)境變量,如果是Web主機(jī)的話)
(5) JSON 配置提供程序 (添加 appsettings.json 配置文件)
(6) JSON 配置提供程序 (添加 appsettings.{Environment}.json 配置文件)
(7) 機(jī)密管理器(僅Windows)
(8) 環(huán)境變量配置提供程序 (未限定前綴)
(9) 命令行配置提供程序 (命令行參數(shù))
按照越后面添加的提供程序優(yōu)先的方式,最終應(yīng)用配置會(huì)覆蓋主機(jī)配置,并且最優(yōu)先是最后添加的命令行配置提供程序,我們可以通過以下方式打印配置系統(tǒng)中所有的配置提供程序,進(jìn)行驗(yàn)證:
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();
最終控制臺(tái)打印出來的結(jié)果如下:
雖然應(yīng)用配置優(yōu)先,會(huì)覆蓋前面的主機(jī)配置,但是有一些變量會(huì)在初始化主機(jī)生成器的時(shí)候就提前進(jìn)行鎖定,并且之后不會(huì)受應(yīng)用配置的影響:
- 應(yīng)用程序名稱
- 環(huán)境名稱,例如 Development、Production 和 Staging
- 內(nèi)容根目錄
- Web 根目錄
- 是否要掃描托管啟動(dòng)程序集以及要掃描哪些程序集。
- 應(yīng)用和庫代碼從 IHostBuilder.ConfigureAppConfiguration 回調(diào)中的 HostBuilderContext.Configuration 讀取的變量。
這里提到環(huán)境名稱,其實(shí)也就是軟件運(yùn)行的環(huán)境,最最基本的也會(huì)分為開發(fā)環(huán)境、生產(chǎn)環(huán)境兩種。軟件運(yùn)行環(huán)境通過環(huán)境變量來設(shè)置,普通的 .NET Core 應(yīng)用環(huán)境變量 key 為NETCORE_ENVIRONMENT
,Web 應(yīng)用環(huán)境變量 key 為ASPNETCORE_ENVIRONMENT
,Web 應(yīng)用下如果兩者同時(shí)存在,后者會(huì)覆蓋前者。軟件應(yīng)用根據(jù)不同的環(huán)境會(huì)有不同的行為邏輯,例如上面講到的 appsettings.{environment}.json 根據(jù)環(huán)境而不同的配置文件,例如之前的 入口文件 文章中講到的 Startup 文件根據(jù)不同環(huán)境的分離配置方式,而我們?cè)诖a中有時(shí)也會(huì)根據(jù)環(huán)境處理不同的邏輯,這時(shí)候我們可以注入 IHostEnvironment
服務(wù),通過它獲取當(dāng)前應(yīng)用的運(yùn)行環(huán)境,入口文件中無論是 WebApplicationBuilder 對(duì)象還是 WebApplication 對(duì)象都包含該類型的屬性。
通過環(huán)境變量設(shè)置當(dāng)前運(yùn)行環(huán)境,其實(shí)環(huán)境變量的值只是一個(gè)字符串,我們可以設(shè)置成任意值,這是運(yùn)行的,.NET Core 框架下 IHostEnvironment
也能夠正常加載到相應(yīng)的環(huán)境名稱,但是 .NET Core 默認(rèn)只提供了對(duì) Development
、Production
和 Staging
三種環(huán)境的判別,以及相應(yīng)的處理邏輯和擴(kuò)展方法,如果是其他的自定義環(huán)境則需要開發(fā)人員自行進(jìn)行相應(yīng)的處理了。和 .NET Core 應(yīng)用環(huán)境相關(guān)的知識(shí)點(diǎn)大家可以看一下官方文檔: 在 ASP.NET Core 中使用多個(gè)環(huán)境 | Microsoft Learn
除了上面的,其他還有一些主機(jī)配置,例如 URLS,但這個(gè)是可以通過應(yīng)用配置設(shè)置的,讀取相應(yīng)的配置值時(shí)也應(yīng)用從應(yīng)用配置讀取。
URLS 配置Web應(yīng)用啟動(dòng)后的訪問地址,這個(gè)配置可以多個(gè)地方設(shè)置,其中命令行參數(shù)最優(yōu)先,其他地方設(shè)置的應(yīng)該被命令行參數(shù)覆蓋。但是如果通過 Kestrel 終結(jié)點(diǎn)方式設(shè)置了 Web 應(yīng)用的訪問地址,那 Kestrel 終結(jié)點(diǎn)的配置將覆蓋其他所有的訪問地址的配置。
如在 appsettings.json 中添加以下配置:
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://localhost:9999"
}
}
}
那么以下幾種方式設(shè)置的 URLS 都會(huì)失效:
- UseUrls
- 命令行上的 --urls
- 環(huán)境變量 ASPNETCORE__URLS
也就是說,就算我們用以下命令啟動(dòng)應(yīng)用,應(yīng)用最終的訪問地址還是以 Kestrel 終結(jié)點(diǎn)配置的為準(zhǔn):
dotnet run --urls="https://localhost:7777"
Kestrel 配置與 URLS 配置不是一個(gè)參數(shù),我們可以通過在命令行或者環(huán)境變量中設(shè)置 kestrel 中間點(diǎn)配置來覆蓋 appsettings.json 中的,這又回到配置提供程序的優(yōu)先級(jí)問題了。
set Kestrel__Endpoints__Https__Url=https://localhost:8888
dotnet run Kestrel__Endpoints__Https__Url=https://localhost:8888
在主機(jī)啟動(dòng)的邏輯中 Kestrel 具備更高的最終優(yōu)先級(jí),但是其實(shí)主機(jī)內(nèi)部是先根據(jù) URLS 創(chuàng)建了一個(gè)終結(jié)點(diǎn),之后又替換為 Kestrel 配置的終結(jié)點(diǎn)的。通過應(yīng)用啟動(dòng)時(shí)的控制臺(tái)輸出可以看出。
這種情況對(duì)于單機(jī)應(yīng)用沒有什么影響,但是對(duì)于使用自動(dòng)服務(wù)發(fā)現(xiàn)的微服務(wù)架構(gòu)而言就可能有問題了,可能導(dǎo)致注冊(cè)服務(wù)注冊(cè)中心的終結(jié)點(diǎn)是第一個(gè),而后應(yīng)用終結(jié)點(diǎn)又被改變,導(dǎo)致注冊(cè)中心記錄的服務(wù)終結(jié)點(diǎn)和實(shí)際的不一致。
4. 自定義配置提供程序
在 .NET Core 配置系統(tǒng)中封裝一個(gè)配置提供程序關(guān)鍵在于提供相應(yīng)的 IconfigurationSource 實(shí)現(xiàn)和 IConfigurationProvider 接口實(shí)現(xiàn),這兩個(gè)接口在上面也有提到了。
IConfigurationSource
IConfigurationSource 負(fù)責(zé)創(chuàng)建 IConfigurationProvider 實(shí)現(xiàn)的實(shí)例。它的定義很簡(jiǎn)單,就一個(gè)Build方法,返回 IConfigurationProvider 實(shí)例:
public interface IConfigurationSource
{
IConfigurationProvider Build(IConfigurationBuilder builder);
}
IConfigurationProvider
IConfigurationProvider 負(fù)責(zé)實(shí)現(xiàn)配置的設(shè)置、讀取、重載等功能,并以鍵值對(duì)形式提供配置。
public interface IConfigurationProvider
{
// 獲取指定父路徑下的直接子節(jié)點(diǎn)Key,然后 Concat(earlierKeys) 一同返回
IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath);
// 當(dāng)該配置提供程序支持更改追蹤(change tracking)時(shí),會(huì)返回 change token
// 否則,返回 null
IChangeToken GetReloadToken();
// 加載配置
void Load();
// 設(shè)置 key:value
void Set(string key, string value);
// 嘗試獲取指定 key 的 value
bool TryGet(string key, out string value);
}
像工作中常用的配置中心客戶端,例如 nacos、consul,都是實(shí)現(xiàn)了對(duì)應(yīng)的配置提供程序,從而將配置中心中的配置無縫地接入到 .NET Core 的配置系統(tǒng)中進(jìn)行使用,和本地配置文件的使用沒有分別。
如果我們需要封裝自己的配置提供程序,推薦直接繼承抽象類 ConfigurationProvider,該類實(shí)現(xiàn)了 IConfigurationProvider 接口,繼承自該類只要實(shí)現(xiàn) Load 方法即可,Load 方法用于從配置來源加載解析配置信息,將最終的鍵值對(duì)配置信息存儲(chǔ)到 Data 中。這個(gè)過程中可參考一下其他已有的配置提供程序的源碼,模仿著去寫自己的東西。
在我們?nèi)粘5南到y(tǒng)平臺(tái)中,總少不了數(shù)據(jù)字典這樣一個(gè)功能,用于維護(hù)平臺(tái)中一些業(yè)務(wù)配置,因?yàn)槭请S業(yè)務(wù)動(dòng)態(tài)擴(kuò)展和變動(dòng)的,很多時(shí)候不會(huì)寫在配置文件,而是維護(hù)在數(shù)據(jù)庫中。以下以這樣一個(gè)場(chǎng)景實(shí)現(xiàn)一個(gè)配置提供程序。
因?yàn)槭且詳?shù)據(jù)庫作為載體來存儲(chǔ)配置信息,所以第一步就是定義實(shí)體類
public class DataDictioaryDO
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
數(shù)據(jù)字典支持多級(jí)級(jí)聯(lián),通過 ParentId 關(guān)聯(lián)上一級(jí),ParentId 為空的即為根節(jié)點(diǎn),如存在下級(jí)節(jié)點(diǎn)則 Value 值可以為空,就算填寫了也無效,最終呈現(xiàn)出來的就是一個(gè)樹結(jié)構(gòu)。
然后就是定義相應(yīng)的數(shù)據(jù)庫訪問上下問 DataDictionaryDbContext
public class DataDictionaryDbContext : DbContext
{
public DbSet<DataDictioaryDO> DataDictioaries { get; set; }
public DataDictionaryDbContext(DbContextOptions<DataDictionaryDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<DataDictioaryDO>().HasKey(e => e.Id);
modelBuilder.Entity<DataDictioaryDO>().Property(e => e.Value).IsRequired(false);
}
}
通過 DbContextOptions 交由外部去配置具體的數(shù)據(jù)庫類型和連接字符串。
之后創(chuàng)建 IConfigurationSource 實(shí)現(xiàn)類,主要就是構(gòu)造函數(shù)中需要傳入數(shù)據(jù)庫配置委托,并且在 Build 實(shí)例化EFDataDictionaryConfigurationProvider 對(duì)象。
public class EFDataDictionaryConfigurationSource : IConfigurationSource
{
private readonly Action<DbContextOptionsBuilder> _action;
public EFDataDictionaryConfigurationSource(Action<DbContextOptionsBuilder> action)
{
_action= action;
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new EFDataDictionaryConfigurationProvider(_action);
}
}
之后通過繼承 ConfigurationProvider 實(shí)現(xiàn) EFDataDictionaryConfigurationProvider,主要邏輯就是從數(shù)據(jù)庫獲取對(duì)應(yīng)的數(shù)據(jù)表,如果表中沒有數(shù)據(jù)則插入默認(rèn)數(shù)據(jù),再通過相應(yīng)的解析器解析數(shù)據(jù)表數(shù)據(jù)生成一個(gè) Dictionary<string, string>
對(duì)象。
public class EFDataDictionaryConfigurationProvider : ConfigurationProvider
{
Action<DbContextOptionsBuilder> OptionsAction { get; }
public EFDataDictionaryConfigurationProvider(Action<DbContextOptionsBuilder> action)
{
OptionsAction = action;
}
public override void Load()
{
var builder = new DbContextOptionsBuilder<DataDictionaryDbContext>();
OptionsAction(builder);
using var dbContext = new DataDictionaryDbContext(builder.Options);
if(dbContext == null)
{
throw new Exception("Null DB Context !");
}
dbContext.Database.EnsureCreated();
if (!dbContext.DataDictioaries.Any())
{
CreateAndSaveDefaultValues(dbContext);
}
Data = EFDataDictionaryParser.Parse(dbContext.DataDictioaries);
}
private void CreateAndSaveDefaultValues(DataDictionaryDbContext context)
{
var datas = new List<DataDictioaryDO>
{
new DataDictioaryDO
{
Id = 1,
Key = "Settings",
},
new DataDictioaryDO
{
Id = 2,
ParentId = 1,
Key = "Provider",
Value = nameof(EFDataDictionaryConfigurationProvider)
},
new DataDictioaryDO
{
Id = 3,
ParentId = 1,
Key = "Version",
Value = "v1.0.0"
}
};
context.DataDictioaries.AddRange(datas);
context.SaveChanges();
}
}
其中,解析器 EFDataDictionaryParser
的代碼如下,主要就是通過遞歸的方式,通過樹形數(shù)據(jù)的 key 構(gòu)建構(gòu)建完整的 key,并將其存入 Dictionary<string,string>
對(duì)象中。
internal class EFDataDictionaryParser
{
private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly Stack<string> _context = new();
private string _currentPath;
private EFDataDictionaryParser() { }
public static IDictionary<string, string> Parse(IEnumerable<DataDictioaryDO> datas) =>
new EFDataDictionaryParser().ParseDataDictionaryConfiguration(datas);
private IDictionary<string, string> ParseDataDictionaryConfiguration(IEnumerable<DataDictioaryDO> datas)
{
_data.Clear();
if(datas?.Any() != true)
{
return _data;
}
var roots = datas.Where(d => !d.ParentId.HasValue);
foreach (var root in roots)
{
EnterContext(root.Key);
VisitElement(datas, root);
ExitContext();
}
return _data;
}
private void VisitElement(IEnumerable<DataDictioaryDO> datas, DataDictioaryDO parent)
{
var children = datas.Where(d => d.ParentId == parent.Id);
if (children.Any())
{
foreach (var section in children)
{
EnterContext(section.Key);
VisitElement(datas, section);
ExitContext();
}
}
else
{
var key = _currentPath;
if (_data.ContainsKey(key))
throw new FormatException($"A duplicate key '{key}' was found.");
_data[key] = parent.Value;
}
}
private void EnterContext(string context)
{
_context.Push(context);
_currentPath = ConfigurationPath.Combine(_context.Reverse());
}
private void ExitContext()
{
_context.Pop();
_currentPath = ConfigurationPath.Combine(_context.Reverse());
}
}
之后為這個(gè)配置提供程序提供一個(gè)擴(kuò)展方法,方便之后的使用,如下:
public static class EFDataDictionaryConfigurationExtensions
{
public static IConfigurationBuilder AddEFDataDictionaryConfiguration(this IConfigurationBuilder builder,
Action<DbContextOptionsBuilder> optionAction)
{
builder.Add(new EFDataDictionaryConfigurationSource(optionAction));
return builder;
}
}
之后在入口文件中將我們的配置擴(kuò)展程序添加到配置系統(tǒng)中,并指定使用內(nèi)存數(shù)據(jù)庫進(jìn)行測(cè)試
using ConfigurationSampleConsole.ConfigProvider;
using Microsoft.EntityFrameworkCore;
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.AddEFDataDictionaryConfiguration(builder =>
{
builder.UseInMemoryDatabase("DataDictionary");
});
})
.Build();
var configuration = host.Services.GetService<IConfiguration>();
Console.WriteLine($"Settings:Provider: {configuration.GetValue<string>("Settings:Provider")}");
Console.WriteLine($"Settings:Version: {configuration.GetValue<string>("Settings:version")}");
host.Run();
最后的控制臺(tái)輸出結(jié)果如下:
以上就是 .NET Core 框架下配置系統(tǒng)的一部分知識(shí)點(diǎn),更加詳盡的介紹大家可以再看看官方文檔。配置系統(tǒng)很多時(shí)候是結(jié)合選項(xiàng)系統(tǒng)儀器一起使用的,下一篇將介紹一下 .NET Core 框架下的選項(xiàng)系統(tǒng)。
?
?文章來源:http://www.zghlxwxcb.cn/news/detail-478223.html
參考文章:
ASP.NET Core 中的配置 | Microsoft Learn
配置 - .NET | Microsoft Learn
理解ASP.NET Core - 配置(Configuration) - xiaoxiaotank
?
?
ASP.NET Core 系列總結(jié):
上一篇:ASP.NET Core — 依賴注入
下一篇:ASP.NET Core — 選項(xiàng)系統(tǒng)文章來源地址http://www.zghlxwxcb.cn/news/detail-478223.html
到了這里,關(guān)于ASP.NET Core —配置系統(tǒng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!