來源: Using multiple instances of strongly-typed settings with named options in .NET Core 2.x
作者: Andrew Lock
譯者: Lamond Lu
.NET Core從1.0版本開始,就已經開始使用Options
模式綁定強類型配置對象。從那時起到現在,這個特性已經獲得了更多的功能。例如在.NET Core 1.1中引入的IOptionsSnapshot
類。使用這個類的好處是,當你的配置文件(例如: appsetting.json)發生變化時,它可以幫助我們自動刷新我們的強類型配置對象。
本篇博客中,我們將討論在依賴注入容器中注冊強類型配置的多個實例的幾種方式。我將特別說明如何使用Named Options
方式來完成注入。
使用強類型配置
Options
模式將POCO對象和IConfiguration
對象綁定,從而實現強類型配置。因為這一過程我已經在之前一篇博文中介紹過,所以這里我就簡述一下。
我們可以將強類型配置對象和配置綁定起來,並注入到你的服務中。
public class SlackApiSettings
{
public string WebhookUrl { get; set; }
public string DisplayName { get; set; }
}
你可以在Startup
類的ConfigureServices
中使用Configure
將強類型配置對象和配置中的一個節點綁定起來。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SlackApiSettings>(Configuration.GetSection("SlackApi"));
}
以上代碼中,Configure
方法將你的配置和SlackApiSettings
對象綁定了起來。除了以上方式,Configure
方法還提供了一個參數為Action
的重載,所以你來可以使用如下的方式綁定配置。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SlackApiSettings>(x => x.DisplayName = "My Slack Bot");
}
你可以通過在服務中注入IOptions
對象來訪問配置好的SlackApiSettings
對象。
public class SlackNotificationService
{
private readonly SlackApiSettings _settings;
public SlackNotificationService(IOptions<SlackApiSettings> options)
{
_settings = options.Value
}
public void SendNotification(string message)
{
// use the settings to send a message
}
}
你可以使用IOptions
屬性,獲取到配置好的強類型對象。
除了以上方式,你還可以注入一個IOptionsSnapshot
接口對象。
使用IOptionsSnapshot
處理配置變化
到目前為止,我所展示的例子都是最典型的用法。但是當我們使用IOption
來讀取強類型配置時,這意味着你的配置在程序生命周期中是不變的。即配置對象只會計算和綁定一次。假如你在程序運行過程中,更改了appSettings.json文件,程序讀取配置時,依然會得到程序啟動時的配置對象,而非你修改過之后的配置對象。
對我個人而言,對於大部分場景,使用IOption
已經能夠解決所有問題。但是如果程序確實需要支持重新加載配置,我們還可以使用ASP.NET Core中的IOptionsSnapshot
。IOptionsSnapshot
和IOptions
使用方法一樣,因此你無需在應用程序中執行任何額外的操作。你只需要使用IOptionsSnapshot
屬性讀取配置對象即可。
public class SlackNotificationService
{
private readonly SlackApiSettings _settings;
public SlackNotificationService(IOptionsSnapshot<SlackApiSettings> options)
{
_settings = options.Value;
}
}
使用以上方式,如果你在程序啟動后,修改了appSettings.json文件,IOptionsSnapshot
會在下一次請求時,更新配置值,你就能獲取到新的配置值了。這里需要注意的是配置值的生命周期是Scoped
, 即在一次請求中,讀取到的配置值都是一樣的。
注意: 並不是所有的配置提供器都支持配置重新加載。文件類型的配置器都沒有問題,但是環境變量配置器就不可以。
重新加載配置在某些情況下可能很有用,但IOptionsSnapshot
還有另一個技巧 - 命名選項(Named Options)。 我們很快就會介紹它們,但首先我們將看一下你可能偶爾遇到的問題,您需要擁有多個設置對象實例。
使用一個強類型配置對象的多個實例
IOption
的典型用例就是針對細粒度的配置。配置系統讓你很容易的為特定服務注入小的,集中的POCO對象。但是如果你需要配置多個具有相同屬性的配置對象時,應該怎么做的?例如,為了將消息發送到Slack
, 你需要一個Webhook Url和一個DisplayName. 當你調用SendNotification(message)
時,SlackNotificationService
會使用這些配置來向指定的Slack Channel中發送消息。
如果你想更新SlackNotificationService以允許你向多個頻道發送消息,該怎么辦?
public class SlackNotificationService
{
public void SendNotificationToDevChannel(string message) { }
public void SendNotificationToGeneralChannel(string message) { }
public void SendNotificationToPublicChannel(string message) { }
}
這里我已經為創建了向不同的頻道發送消息的方法。但是問題是,我該如何為每個頻道配置其對應的Webhook Url
和DisplayName
。
為了提供一些思路,這里我們假設我們的配置文件結構是這樣的。
{
"SlackApi": {
"DevChannel" : {
"WebhookUrl": "https://hooks.slack.com/T1/B1/111111",
"DisplayName": "c0mp4ny 5l4ck b07"
},
"GeneralChannel" : {
"WebhookUrl": "https://hooks.slack.com/T2/B2/222222",
"DisplayName": "Company Slack Bot"
},
"PublicChannel" : {
"WebhookUrl": "https://hooks.slack.com/T3/B3/333333",
"DisplayName": "Professional Looking name"
}
}
為了在SlackNotificationService
中讀取到響應的配置,這里有3種可行的方案。
1. 創建父類配置對象
第一種方式就是擴展SlackApiSettings
類,在其中包含各個頻道的配置屬性。
public class SlackApiSettings
{
public ChannelSettings DevChannel { get; set; }
public ChannelSettings GeneralChannel { get; set; }
public ChannelSettings PublicChannel { get; set; }
public class ChannelSettings
{
public string WebhookUrl { get; set; }
public string DisplayName { get; set; }
}
}
這里我創建了一個內嵌類ChannelSettings
, 並在SlackApiSettings
類中添加了針對3個Slack Channel的配置。這個新的配置類,正確反映了appSettings.json文件中的配置結構。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SlackApiSettings>(Configuration.GetSection("SlackApi"));
}
在SlackNotificationService
中,我們還是和之前一樣注入的單個配置類對象
public class SlackNotificationService
{
private readonly SlackApiSettings _settings;
public SlackNotificationService(IOptions<SlackApiSettings> options)
{
_settings = options.Value;
}
}
這種配置方式的優點是易於理解,我們為每個Slack Channel配置了獨立的強類型配置。缺點是如果要支持新的Slack Channel, 你每次都需要修改SlackApiSettings
類。
2. 為每個Channel配置創建單獨的配置類
另外一種方法是我們可以獨立配置每個Slack Channel。我們分開配置不同的Slack Channel, 並把他們注入到SlackNotificationService
服務中。
例如,我們將ChannelSettings
類變成一個抽象類
public abstract class ChannelSettings
{
public string WebhookUrl { get; set; }
public string DisplayName { get; set; }
}
然后每一個Slack Channel的配置類繼承ChannelSettings
類。
public class DevChannelSettings: ChannelSettings { }
public class GeneralChannelSettings: ChannelSettings { }
public class PublicChannelSettings: ChannelSettings { }
為了配置不同的Slack Channel, 我們需要在程序啟動時分別綁定不同的配置節點。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<DevChannelSettings>(Configuration.GetSection("SlackApi:DevChannel"));
services.Configure<GeneralChannelSettings>(Configuration.GetSection("SlackApi:GeneralChannel"));
services.Configure<PublicChannelSettings>(Configuration.GetSection("SlackApi:PublicChannel"));
}
由於不同的Slack Channel擁有不同的配置,所以我們需要分開將他們注入到SlackNotificationService
中。
public class SlackNotificationService
{
private readonly DevChannelSettings _devSettings;
private readonly GeneralChannelSettings _generalSettings;
private readonly PublicChannelSettings _publicSettings;
public SlackNotificationService(
IOptions<DevChannelSettings> devOptions
IOptions<GeneralChannelSettings> generalOptions
IOptions<PublicChannelSettings> publicOptions)
{
_devSettings = devOptions;
_generalSettings = generalOptions;
_publicSettings = publicOptions;
}
}
這種方式的好處是當你需要添加新的Slack Channel配置時,你不需要去修改之前定義的配置類結構,你只需要添加一個針對新Slack Channel的配置類。但是它也讓事情更加復雜了,你不僅需要為每個新的Slack Channel配置類綁定配置的節點, 還需要修改SlackNotificationService
的構造函數添加對新Slack Channel配置類的依賴。
3. 使用Named Options
第三種方案就是本文的主題Named Options
。Named Options
翻譯過來就是命名配置,和它的字面意思一樣,我們可以使用它為每個強類型配置對象起一個唯一的名稱,並在使用時通過指定唯一名稱來獲取所需的強類型配置對象。
使用Named Options
, 你可以擁有同一個強類型配置類的不同實例,並獨立配置他們。這意味着,我們可以繼續使用本文開頭所定義的SlackApiSettings
類。
public class SlackApiSettings
{
public string WebhookUrl { get; set; }
public string DisplayName { get; set; }
}
區別是,當我們配置強類型配置對象的代碼有所不同。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SlackApiSettings>("Dev", Configuration.GetSection("SlackApi:DevChannel"));
services.Configure<SlackApiSettings>("General", Configuration.GetSection("SlackApi:GeneralChannel"));
services.Configure<SlackApiSettings>("Public", Configuration.GetSection("SlackApi:PublicChannel"));
}
我們使用的Configure
的2個參數的重載方法,其中第一個參數指定了一個唯一名稱,第二個參數指定了配置文件中對應的節點名稱。
為了使用這些命名配置(Named Options), 我們需要在SlackNotificationService
類的構造函數中注入IOptionSnapshot
對象,而不是我們之前使用的IOption
對象。IOptionsSnapshot
方法允許我們通過傳入唯一名稱,獲取對應的強類型配置對象。
public class SlackNotificationService
{
private readonly SlackApiSettings _devSettings;
private readonly SlackApiSettings _generalSettings;
private readonly SlackApiSettings _publicSettings;
public SlackNotificationService(IOptionsSnapshot<SlackApiSettings> options)
{
_devSettings = options.Get("Dev");
_generalSettings = options.Get("General");
_publicSettings = options.Get("Public");
}
}
這種方式最大的好處是,當添加新的Slack Channel時,你不需要添加任何新的配置類,你只需要針對新的Slack Channel配置一個新的SlackApiSetting
對象即可。缺點是從SlackNotificationService
的構造函數上,你已經不知道它對應的配置節點是哪個了。
總結
在本篇博客中,我們介紹了如何在ASP.NET Core中使用強類型配置。然后我們討論了如何在ASP.NET Core的依賴注入容器中添加強類型配置對象的多個實例。這里我們講解了使用3種不同的方式
- 創建父類配置對象
- 為每個Channel配置創建單獨的配置類
- 使用Named Options