1. 程式人生 > >.NET Core 2.x中使用Named Options處理多個強型別配置例項

.NET Core 2.x中使用Named Options處理多個強型別配置例項

來源: 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 .Value 屬性,獲取到配置好的強型別物件。

除了以上方式,你還可以注入一個IOptionsSnapshot 介面物件。

使用IOptionsSnapshot 處理配置變化

到目前為止,我所展示的例子都是最典型的用法。但是當我們使用IOption 來讀取強型別配置時,這意味著你的配置在程式生命週期中是不變的。即配置物件只會計算和繫結一次。假如你在程式執行過程中,更改了appSettings.json檔案,程式讀取配置時,依然會得到程式啟動時的配置物件,而非你修改過之後的配置物件。

對我個人而言,對於大部分場景,使用IOption 已經能夠解決所有問題。但是如果程式確實需要支援重新載入配置,我們還可以使用ASP.NET Core中的IOptionsSnapshot IOptionsSnapshot IOptions 使用方法一樣,因此你無需在應用程式中執行任何額外的操作。你只需要使用IOptionsSnapshot .Value 屬性讀取配置物件即可。

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 UrlDisplayName

為了提供一些思路,這裡我們假設我們的配置檔案結構是這樣的。

{
  "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 OptionsNamed 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 .Get(name) 方法允許我們通過傳入唯一名稱,獲取對應的強型別配置物件。

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