C#中的選項模式
[[academy-video-youtube({"vid": "ko1Ie9gDydY", "start_time": "0", "title": "The Options Pattern in C#", "creator": "Tim Corey", "length": "10m 15s"})]]
.NET應用程式中的組態通常儲存在appsettings.json、環境變數或使用者的祕密中。 在不修改文件的情況下,以乾淨、可測試的方式將這些組態資料導入到您的類中正是選項模式所解決的問題。 與手動讀取JSON鍵或傳遞原始字串相比,您可以將配置節綁定到強型別C#類,並讓依賴注入將其傳遞到需要的地方。
在他的视频"The Options Pattern in C#"中,Tim Corey介紹了選項模式的三種變體(IOptionsMonitor),並在Blazor伺服器應用程式中演示每一種,說明如何新增驗證,以便在組態遺漏或格式不正確時快速失敗。 如果您正在構建任何從組態文件讀取且使用依賴注入的.NET專案,這種模式是基礎。
設定:一個POCO模型和appsettings.json
[0:34 - 1:46] Tim開始使用Blazor網路應用程式(伺服器端渲染,無客戶端互動),其已經配備了兩個部分。 第一部分是在appsettings.json中的一節,該部分在CloudInfo節下有三個鍵值對:
// appsettings.json
{
"CloudInfo": {
"Storage": "https://storage.example.com",
"Website": "https://www.example.com",
"API": "https://api.example.com"
}
}// appsettings.json
{
"CloudInfo": {
"Storage": "https://storage.example.com",
"Website": "https://www.example.com",
"API": "https://api.example.com"
}
}第二部分是普通的C#類(POCO),其屬性與JSON鍵相對應:
public class CloudInfoOptions
{
public string Storage { get; set; }
public string Website { get; set; }
public string API { get; set; }
}public class CloudInfoOptions
{
public string Storage { get; set; }
public string Website { get; set; }
public string API { get; set; }
}Tim指出,組態來源不一定是appsettings.json具體。 它可以是secrets.json或任何組合。 選項模式從註冊到應用程式的任意組態提供者讀取,POCO上的屬性名稱與JSON鍵按約定匹配。
在Program.cs中註冊選項
[2:25 - 3:15] 將組態與依賴注入連接需要在Program.cs中調用兩次方法:
// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo");// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo");AddOptions<t>()註冊型別與DI。 appsettings.json中的JSON鍵。 如果節名稱和類名稱不匹配,則字串參數是框架用來查找正確資料的方式。
IOptions: 單例方式
[3:15 - 5:18] 註冊到位後,Tim使用IOptions<CloudInfoOptions>將組態注入到Blazor頁面中:
@inject IOptions<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
var options = CloudConfig.Value;
// options.Storage, options.Website, options.API are available
}
}@inject IOptions<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
var options = CloudConfig.Value;
// options.Storage, options.Website, options.API are available
}
}關鍵細節是IOptions<t>被註冊為單例。 組態值在應用程式啟動時被讀取一次,並在整個進程的生命周期中快取在記憶體中。 如果您在應用程式運行時更改IOptions將不會反映這些更改。
對於大多數應用程式,這是正確的選擇。組態在運行時很少更改,單例的生命周期意味著每次請求的開銷為零。
IOptionsSnapshot: 範圍內重新載入
[5:18 - 6:55] Tim將IOptionsSnapshot以演示範圍變體:
@inject IOptionsSnapshot<CloudInfoOptions> CloudConfig@inject IOptionsSnapshot<CloudInfoOptions> CloudConfigIOptionsSnapshot<t>在每次請求時重新讀取組態(範圍生命周期)。 如果您在應用程式運行時修改appsettings.json,下一次網路請求將會獲得新值。 先前的請求保留其自己的快照,因此不會在請求期間出現不一致。
這對於需要在不重新啟動的情況下生效的組態更改的應用程式很有用,例如切換功能標記、更新API端點或旋轉儲存連接字串。 權衡是每次請求需要重新讀取組態的小成本,這對大多數工作負載來說可以忽略不計。
IOptionsMonitor: 實時變更通知
[6:55 - 8:34] 第三個變體,IOptionsMonitor<t>,比快照重新載入走得更遠。 它主動監視組態變更,並在值更新時觸發回調:
@inject IOptionsMonitor<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
// Current values
var current = CloudConfig.CurrentValue;
// Register a callback for live changes
CloudConfig.OnChange(updatedOptions =>
{
// React to configuration changes in real time
});
}
}@inject IOptionsMonitor<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
// Current values
var current = CloudConfig.CurrentValue;
// Register a callback for live changes
CloudConfig.OnChange(updatedOptions =>
{
// React to configuration changes in real time
});
}
}不同於IOptionsMonitor能夠在長期運行操作中檢測變更。 Tim指出這對於背景服務、SignalR hub或任何單個HTTP請求超過生命週期的元件最為相關。
新增驗證
[8:34 - 10:05] Tim覆蓋的最後一片是驗證。 選項模式支援內聯驗證規則,這些規則在首次存取配置時運行:
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo")
.Validate(opts =>
!string.IsNullOrEmpty(opts.Storage) &&
!string.IsNullOrEmpty(opts.Website) &&
!string.IsNullOrEmpty(opts.API));builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo")
.Validate(opts =>
!string.IsNullOrEmpty(opts.Storage) &&
!string.IsNullOrEmpty(opts.Website) &&
!string.IsNullOrEmpty(opts.API));Tim通過移除Storage的值來演示失敗案例,然後啟動應用程式。 結果是立即的未處理異常:"CloudInfoOptions驗證失敗。"應用程式拒絕在組態不完整的情況下啟動,這正是您想要的行為。 在啟動時發現缺失值遠比在凌晨2點在生產中觸發空引用要好。
對於更複雜的驗證場景(跨屬性檢查、條件規則),您可以連結多個IValidateOptions<t>作為專用驗證類別。
總結:三個介面,一個模式
[10:05 - 10:10] 選項模式提供配置儲存與消耗之間的明確分離。 IOptions涵蓋了配置是靜態的絕大多數用例。 IOptionsSnapshot處理需要在請求之間獲取變更的應用程式。 IOptionsMonitor服務於需要實時了解組態更新的長期運行元件。 驗證確保您的應用程式在缺少所需值時響亮地失敗。
結論
[10:10 - 10:15] 總結:在.Validate()以便在啟動時發現缺失的值,而不是在運行時。
該模式適用於任何.NET專案型別:Blazor、 ASP.NET Core、 控制台應用程式、 worker服務。 在任何地方註冊都是一樣的。
範例提示:如果您不確定要使用哪一個介面,從IOptions<t>開始。 這是最簡單的,對每個請求零開銷,適用於多數在啟動時設置且不改變配置的案例。 僅在有具體需求需要運行時重新載入時才移動到IOptionsMonitor。

