使用.NET 6開發TodoList應用(26)——實現Configuration和Option的強類型綁定
系列導航及源代碼
需求
在上一篇文章使用.NET 6開發TodoList應用(25)——實現RefreshToken中,我們通過使用Configuration獲取方法GetSection
拿到寫在appsettings.Development.json
中JWT的相關配置字段,這樣實現沒有問題,但是我們有更好的選擇:通過使用強類型的Configuration綁定方法,或者通過Options相關方法來實現。在本文中我們將會分別來看一下這兩種方法的實現。
目標
實現配置字段的強類型綁定,分別通過Configuration綁定和Options實現。
原理與思路
要實現強類型綁定,首先我們需要定義這個配置類型。然後根據需求,選擇使用Configuration綁定實現或者使用Options配置實現。二者實現的功能上有一些區別:使用Options模式提供了更多的功能,如校驗、熱加載,也更方便進行測試。
實現
定義配置類型
根據我們在appsettings.Development.json
中的配置:
"JwtSettings": {
"validIssuer": "TodoListApi",
"validAudience": "//localhost:5050",
"expires": 5
}
在Application/Configurations
中添加JwtConfiguration
類如下:
JwtConfiguration.cs
namespace TodoList.Application.Common.Configurations;
public class JwtConfiguration
{
public string Section { get; set; } = "JwtSettings";
public string? ValidIssuer { get; set; }
public string? ValidAudience { get; set; }
public string? Expires { get; set; }
}
方法1: 通過Configuration綁定實現
修改Infrastructure
項目中的DependencyInjection
添加認證方法的邏輯:
DependencyInjection.cs
// 省略其他...
// 添加認證方法為JWT Token認證
var jwtConfiguration = new JwtConfiguration();
configuration.Bind(jwtConfiguration.Section, jwtConfiguration);
services
.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
// 改為使用配置類成員獲取
ValidIssuer = jwtConfiguration.ValidIssuer,
ValidAudience = jwtConfiguration.ValidAudience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("SECRET") ?? "TodoListApiSecretKey"))
};
});
修改IdentityService
中的邏輯,添加一個私有字段
IdentityService.cs
// 添加JWT配置類字段
private readonly JwtConfiguration _jwtConfiguration;
// 構造函數中進行初始化
// 初始化配置對象
_jwtConfiguration = new JwtConfiguration();
configuration.Bind(_jwtConfiguration.Section, _jwtConfiguration);
並將所有之前使用json對象獲取字段值的地方都修改成通過私有字段的成員變量獲取:
// 省略其他...
ValidIssuer = _jwtConfiguration.ValidIssuer,
ValidAudience = _jwtConfiguration.ValidAudience
驗證如下,我們還是通過獲取refresh token來檢查配置是否成功:
看起來沒什麼問題。下面我們看第二種方法,也是相對比較推薦的做法。
方法2: 通過IOptions配置實現
我們在Infrastructure/DependencyInjection.cs
中添加使用IOptions的配置:
DependencyInjection.cs
// 使用IOptions配置
services.Configure<JwtConfiguration>(configuration.GetSection("JwtSettings"));
然後通過依賴注入的方式去修改IdentityService
:
IdentityService.cs
public IdentityService(
ILogger<IdentityService> logger,
IConfiguration configuration,
UserManager<ApplicationUser> userManager,
IOptions<JwtConfiguration> jwtOptions)
{
_logger = logger;
_userManager = userManager;
// 初始化配置對象
_jwtConfiguration = jwtOptions.Value;
}
其他的不需要再進行修改。下面來驗證一下效果,驗證方法和剛才一致:
可以看到依然是沒有問題的。
一點擴展
擴展1: 關於配置熱加載
綜合我們剛才提到的和所演示的可以看到,我們並沒有演示關於配置熱加載的功能,如果在程序的運行過程中,我們希望配置文件的改動能夠直接反映到應用中,不需要重啟應用,可以預想到這個功能還是很重要的。
這個功能是通過IOptionsSnapshot
或者IOptionsMonitor
來實現的,我們所需要做的就是在依賴注入的時候使用IOptionsSnapshot<JwtConfiguration>
或者IOptionsMonitor<JwtConfiguration>
代替我們之前使用的IOptions<JwtConfiguration>
。在替換的過程中之需要注意以下兩點即可:
IOptionsSnapshot
本身是註冊為ScopedService
,所以不能注入到SingletonService
中使用;IOptionsMonitor
本身註冊為了SingletonService
,所以可以注入到SingletonService
中使用,但是在取值的時候不是使用Value
而是使用CurrentValue
。
我們使用IOptionsMonitor
來舉例子驗證,只需要修改IdentityService
中構造函數的注入部分:
IdentityService.cs
public IdentityService(
ILogger<IdentityService> logger,
UserManager<ApplicationUser> userManager,
IOptionsMonitor<JwtConfiguration> jwtOptions)
{
_logger = logger;
_userManager = userManager;
// 使用IOptionsMonitor加載配置
_jwtConfiguration = jwtOptions.CurrentValue;
}
重新運行項目,我們先直接請求Token:
解析出的payload如下,過期時間是之前設置的5分鐘後:
在不重啟應用的情況下,我們去修改appsettings.Development.json
中關於過期時間的配置,將過期時間設置為10分鐘:
"JwtSettings": {
"validIssuer": "TodoListApi",
"validAudience": "//localhost:5050",
"expires": 10
}
再次執行獲取Token的請求,查看Header里的Date字段值:
並把token解析:
這個過期時間已經變成10分鐘後了,大家可以自己動手試一下。
擴展2: 關於相同類型的多個配置Section處理
有一種情況是在appsettings.Development.json
中我們可能會做這樣的配置:
"JwtSettings": {
"validIssuer": "TodoListApi",
"validAudience": "//localhost:5050",
"expires": 5
},
"JwtApiV2Settings": {
"validIssuer": "TodoListApiV2",
"validAudience": "//localhost:5050",
"expires": 10
}
面對這種情況,我們可以在進行IOptions配置時指定配置名稱,像這樣:
// 使用IOptions配置
services.Configure<JwtConfiguration>("JwtSettings", configuration.GetSection("JwtSettings"));
services.Configure<JwtConfiguration>("JwtApiV2Settings", configuration.GetSection("JwtApiV2Settings"));
而在需要注入使用的地方也指定對應要進行配置的名稱即可:
// 使用IOptionsMonitor加載配置
_jwtConfiguration = jwtOptions.Get("JwtApiV2Settings");
這樣就可以正確地使用相應的配置了,就不再繼續演示了。
總結
關於三種IOptions的對比見下表:
類型 | 依賴注入類型 | 是否支持配置熱加載 | 配置加載更新時機 | 是否支持名稱配置 |
---|---|---|---|---|
IOptions<T> |
Singleton注入 | 否 | 只在程序運行開始時綁定一次,以後每次獲取的都是相同值 | 否 |
IOptionsSnapshot<T> |
Scoped注入 | 是 | 每次請求時都會重新加載配置 | 是 |
IOptionsMonitor<T> |
Singleton注入 | 是 | 配置的值被緩存起來了,當原始配置發生變化時立即發生更新 | 是 |
在本文中我們介紹了如何使用強類型綁定配置項,以及如何實現配置的熱加載。對於沒有涉及到的諸如配置項的校驗等內容(可以通過Annotation實現校驗)可以參考官方文檔:Options validation