1. 選項 前面講完了.NET Core 下的配置系統,我們可以通過 IConfiguration 服務從各種來源的配置中讀取到配置信息,但是每次要用的時候都通過 Iconfiguration 讀取配置文件會比較不方便,而且效率低。.NET Core 體系下提供了一個選項系統,該功能用於實現以強類型 ...
1. 選項
前面講完了.NET Core 下的配置系統,我們可以通過 IConfiguration 服務從各種來源的配置中讀取到配置信息,但是每次要用的時候都通過 Iconfiguration 讀取配置文件會比較不方便,而且效率低。.NET Core 體系下提供了一個選項系統,該功能用於實現以強類型的方式對程式配置信息進行訪問,並且可以將選項類註入到依賴註入容器中進行管理和使用。
在進行配置信息的強類型選項綁定的時候,需要一個相應的選項類,該類推薦按 {Object}Options
命名規則進行命名,有以下特點:
- 必須非抽象類
- 必須包含公共無參的構造函數
- 類中需要與配置項進行綁定的屬性必須擁有 public 的 get、set 訪問器,並且屬性的命名必須與配置鍵一直,不區分大小寫
- 要確保配置項能夠轉換到其綁定的屬性類型
- 該類的欄位不會被綁定
2. 選項配置方式
2.1 手動綁定
IConfiguration 服務通過 ConfigurationBinder 類擴展了 Get 和 Bind 兩個方法,這兩個方法可以將配置信息綁定到選項類上。這種方式在之前的 ASP.NET Core - 配置系統之配置讀取 一章有講到過,這裡再做一下演示:
首先在配置文件中添加以下節點:
"Blog": {
"Title": "ASP.NET Core Options",
"Content": "This is a blog about Options System in ASP.NET Core Framework.",
"CreateTime": "2022-12-06"
}
之後定義一個選項類:
public class BlogOptions
{
public const string Blog = "Blog";
public string Title { get; set; }
public string Content { get; set; }
public DateTime CreateTime { get; set; }
}
然後,在任何可以獲取到 IConfiguration 服務的地方都可以通過 IConfiguration 服務進行綁定:
using OptionsSample.Options;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var blog1 = new BlogOptions();
app.Configuration.GetSection(BlogOptions.Blog).Bind(blog1);
var blog2 = app.Configuration.GetSection(BlogOptions.Blog).Get<BlogOptions>();
Console.WriteLine(JsonSerializer.Serialize(blog1));
Console.WriteLine(JsonSerializer.Serialize(blog2));
app.Run();
這種方式依舊有些不方便,雖然也有好處,能夠監測到配置的修改,在應用運行中同步改變(如果相應的配置處理程式支持變更重載的化),但每次都要指定相應的節點,每次都要實時構建新的選項對象,並且選項系統也能做到配置更改時更新選項。
2.2 依賴註入配置
2.2.1 配置文件節點轉換選項
除了手動綁定的方式外,我們還可以在應用啟動的時候讀取相應的配置節點,配置成Options並同時註冊到依賴註入容器中,由依賴註入容器管理其生命周期,並且多次使用。我們可以像註冊其他的依賴註入關係一樣,在入口文件中通過 IServiceCollection 的 Configure 擴展方法進行配置。
// 通過配置文件讀取某一配置節點
//builder.Services.Configure<BlogOptions>(builder.Configuration.GetSection(BlogOptions.Blog));
這裡通過獲取特定的配置節點,並將其配置為選項,之後就可以在任何能夠進行依賴註入的地方使用註入的 BlogOptions 選項類了。
var blogOption = app.Services.GetRequiredService<IOptions<BlogOptions>>().Value;
Console.WriteLine(JsonSerializer.Serialize(blogOption));
這裡使用到的 Configure 方法是 OptionsConfigurationServiceCollectionExtensions 類中的擴展方法,選項系統中有好幾個同名的 Configure 方法,但是這些是並不是同一個方法的重載,下麵會詳細講到。
2.2.2 硬編碼配置選項
除了從配置文件中讀取配置節點轉換為選項之外,我們也可以直接在代碼中硬編碼指定選項內容,並註入到容器之中。
//硬編碼的方式設置配置信息,也可以在這裡讀取資料庫信息
builder.Services.Configure<BlogOptions>(option =>
{
option.Title = "test";
option.Content = "test hard code option";
option.CreateTime = DateTime.Now;
});
這種情況用得不多,在這種情況下我們可以進行額外的一些邏輯,例如從資料庫中獲取一些信息。這裡的 Configure 方法是 OptionsServiceCollectionExtensions 擴展類中的方法。值得註意的是,在同時使用上面兩個方法配置同一個選項類的情況下,硬編碼配置的方式優先。
2.2.3 使用DI服務配置選項
在某些場景下,選項的配置需要比較複雜的邏輯,會依賴容器中的其他服務,我們還可以使用以下方式:
builder.Services.AddOptions<BlogOptions>()
// 這裡可以通過 Configure 方法指定需要的服務, IServiceProvider 只是一個示例
.Configure<IServiceProvider>((option, service) => // 接收的的第一個參數選項類對象,後面依次是所註入的服務
{
// 通過註入的服務執行相應的邏輯
option.Title = "test DI Configure";
option.Content = "test DI Configure";
option.CreateTime = DateTime.Now;
});
這裡的 Configure 方法,與上面的不一樣,不再是 IServiceCollection 的擴展方法,而是 OptionsBuilder
當使用這種方式和上面的硬編碼的方式同時配置同一個選項類的情況下,哪部分代碼後執行,最後選項類的內容就以哪部分為準。
2.2.4 命名選項
在一些情況下,應用中是存在多份配置結構相同,但具體配置值不同的配置信息的,例如以下的情況:
"FirstBlog": {
"Title": "ASP.NET Core Options",
"Content": "This is a blog about Options System in ASP.NET Core Framework.",
"CreateTime": "2022-12-06"
},
"SecondBlog": {
"Title": "ASP.NET Core Configuration",
"Content": "This is a blog about Configuration System in ASP.NET Core Framework.",
"CreateTime": "2022-12-08"
}
這種情況下,兩個配置節點其實可以用同一個選項類接收,它們的結構是一樣的。而命名選項就是為了應對這種情況,選項系統中允許為當前配置的選項命名,而選項系統區分同一個類型的不同選項也是根據名字進行區分的。事實上,.NET Core 中所有 Options 都是命名選項,當沒有顯式指定名字時(如上面講到的預設配置方式),使用的名字預設是Options.DefaultName,即string.Empty。
builder.Services.Configure<BlogOptions>("First", builder.Configuration.GetSection("FirstBlog"));
builder.Services.Configure<BlogOptions>("Second", builder.Configuration.GetSection("SecondBlog"));
這裡要說明的是,上面我們從依賴註入容器中解析 IOptions
var blog = app.Services.GetRequiredService<IOptions<BlogOptions>>().Value;
Console.WriteLine(JsonSerializer.Serialize(blog));
var firstBlog = app.Services.GetRequiredService<IOptionsMonitor<BlogOptions>>().Get("First");
Console.WriteLine(JsonSerializer.Serialize(firstBlog));
var secondBlog = app.Services.GetRequiredService<IOptionsMonitor<BlogOptions>>().Get("Second");
Console.WriteLine(JsonSerializer.Serialize(secondBlog));
2.2.4 後期配置
後期配置是指當我們通過前面的方法對一個選項類進行配置之後,可能還會因為其他業務邏輯需要對其中的配置信息進行修改,這時候我們可以通過後期配置對選項系統中已配置的選項內容進行替換。後期配置在所有的OptionsServiceCollectionExtensions.Configure 執行完成之後執行,即配置系統不管代碼順序,會先完成所有選項的配置,再執行後期配置。
builder.Services.Configure<BlogOptions>("First", builder.Configuration.GetSection("FirstBlog"));
builder.Services.PostConfigure<BlogOptions>("First", options =>
{
options.Title = "Post Config";
});
var firstBlog = app.Services.GetRequiredService<IOptionsMonitor<BlogOptions>>().Get("First");
Console.WriteLine(JsonSerializer.Serialize(firstBlog));
除了 PostConfigure 方法外,還有 PostConfigureAll 方法,前者對單一選項類單一命名的選項對象進行後期配置,如果沒有指定名稱則是預設名稱,後者會對統一選項類下的不同命名選項統一進行配置。
builder.Services.Configure<BlogOptions>("First", builder.Configuration.GetSection("FirstBlog"));
builder.Services.Configure<BlogOptions>("Second", builder.Configuration.GetSection("SecondBlog"));
builder.Services.PostConfigureAll<BlogOptions>(options =>
{
options.Title = "Post Config";
});
var firstBlog = app.Services.GetRequiredService<IOptionsMonitor<BlogOptions>>().Get("First");
Console.WriteLine(JsonSerializer.Serialize(firstBlog));
var secondBlog = app.Services.GetRequiredService<IOptionsMonitor<BlogOptions>>().Get("Second");
Console.WriteLine(JsonSerializer.Serialize(secondBlog));
通過 IOptionsMonitor
參考文章:
ASP.NET Core 中的選項模式 | Microsoft Learn
選項模式 - .NET | Microsoft Learn
面向 .NET 庫創建者的選項模式指南 - .NET | Microsoft Learn
理解ASP.NET Core - 選項(Options)
ASP.NET Core 系列:
目錄:ASP.NET Core 系列總結
上一篇:ASP.NET Core - 配置系統之自定義配置提供程式