代码改变世界

[转]在.NET Core 2.x中将多个强类型设置实例与命名选项一起使用

2019-10-11 18:47  音乐让我说  阅读(274)  评论(0编辑  收藏  举报

自1.0版之前,ASP.NET Core已使用“ 选项”模式配置强类型设置对象。从那时起,该功能获得了更多功能。例如,引入了ASP.NET Core 1.1 IOptionsSnapshot,它允许在基础IConfigurationRoot更改时(例如,在更改appsettings.json文件时)更新强类型选项。

在本文中,我将讨论您要在依赖项注入容器中注册强类型设置对象的多个实例时的选项。特别是,我展示了如何使用命名选项以不同的名称注册每个已配置的对象。

我将首先回顾一下您通常如何将选项模式与强类型设置,IOptions<T>界面和IOptionsSnapshot<T>界面一起使用。然后,我将探讨三种可能的方式来在DI容器中注册多个强类型设置的实例。

使用强类型设置

选项模式通过将POCO对象绑定到对象来允许使用强类型设置IConfiguration在最近的文章中介绍了此过程,因此在这里我会相对简短。

我们将从一个强类型的设置对象开始,您可以将其绑定到配置并注入到您的服务中:

public class SlackApiSettings  
{
    public string WebhookUrl { get; set; }
    public string DisplayName { get; set; }
}

您可以绑定,要在配置部分Startup.ConfigureServices使用Configure<T>()

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<SlackApiSettings>(Configuration.GetSection("SlackApi")); 
}

Configure方法将您的配置(从appsettings.json加载,环境变量,用户密码等)绑定到该SlackApiSettings对象。您还可以配置一个IOptions<>使用过载的对象Configure()接受一个Action<>,而不是一个配置部分,所以你可以在代码,例如使用配置

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<SlackApiSettings>(x => x.DisplayName = "My Slack Bot"); 
}

您可以SlackApiSettings通过将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<T>.Value另外,您也可以注入一个IOptionsSnapshot<T>

处理配置更改 IOptionsSnapshot<T>

到目前为止,我展示的示例可能是最典型的用法(尽管避免对IOptions<T>服务的依赖也很常见)。使用IOptions<T>强类型配置假定你的配置是固定的应用程序的生命周期。配置值被计算并绑定到您的POCO对象一次;例如,如果您以后更改appsettings.json文件,则更改不会显示在您的应用中。

就个人而言,我发现几乎所有我的应用程序都可以。但是,如果您确实需要支持配置的重新加载,则可以通过该IOptionsSnapshot<T>界面进行。该接口与该接口同时配置IOptions<T>,因此您无需执行任何其他操作即可在您的应用程序中使用它。只需将其注入您的服务中,然后访问该IOptionsSnapshot<T>.Value属性上配置的设置对象

public class SlackNotificationService
{
    private readonly SlackApiSettings _settings;
    public SlackNotificationService(IOptionsSnapshot<SlackApiSettings> options)
    {
        _settings = options.Value
    }
}

如果以后更改配置的值(例如,通过编辑appsettings.json文件),IOptionsSnapshot<T>则会在下一个请求时更新强类型的配置,您将看到新的值。请注意,配置值本质上具有“作用域”生存期-您将IOptionsSnapshot<T>在请求的生存期内看到相同的配置值

并非所有配置提供程序都支持配置重载。例如,基于文件的提供程序都可以,但是环境变量提供程序则不可以。

重新加载配置在某些情况下可能很有用,但IOptionsSnapshot<T>还有另外一个窍门-名为options。我们将尽快与他们联系,但首先我们将探讨一个问题,您可能偶尔会遇到需要设置对象的多个实例的情况。

使用强类型设置对象的多个实例

我看到的典型用例IOptions<T>是细粒度的强类型设置。绑定系统使您可以轻松地为每个特定服务注入小型,集中的POCO对象。

但是,如果要配置多个具有相同属性的对象该怎么办。例如,考虑SlackApiSettings我到目前为止使用过的。要将消息发布到Slack,您需要一个WebHook URL和一个显示名称。SlackNotificationService使用这些值将消息发送到特定的频道在农闲时调用SendNotification(message)

如果您想更新,SlackNotificationService以允许您将消息发送到多个渠道怎么办例如:

public class SlackNotificationService
{
    public void SendNotificationToDevChannel(string message) { }
    public void SendNotificationToGeneralChannel(string message) { }
    public void SendNotificationToPublicChannel(string message) { }
}

我添加方法这里三个不同的渠道DevGeneralPublic问题是,我们如何为每个通道配置WebHook URL和显示名称?为了提供一些上下文,我假设我们将配置绑定到一个看起来像这样的单个appsettings.json文件:

{
  "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; 的设置有一些可用选项我将在下面逐步介绍其中的三个。

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配置这些设置很简单,因为我小心地匹配了appsettings.jsonSlackApiSettings层次结构:

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
    }
}

这种方法的优点是易于理解正在发生的事情,并且它提供对每个通道设置的强类型访问。不利的一面是,增加对另一个频道的支持涉及到编辑SlackApiSettings类,在某些情况下这可能是不可能的(或不希望的)。

2.为每个通道创建单独的类

另一种方法是将每个通道的设置视为独立的。我们将分别配置和注册每个通道设置对象,然后将它们全部注入SlackNotificationService例如,我们可以从一个抽象ChannelSettings开始

public abstract class ChannelSettings  
{
    public string WebhookUrl { get; set; }
    public string DisplayName { get; set; }
}

并由此导出我们的各个频道设置:

public class DevChannelSettings: ChannelSettings { }
public class GeneralChannelSettings: ChannelSettings { }
public class PublicChannelSettings: ChannelSettings { }

要配置我们的选项,我们需要调用Configure<T>每个通道,传入要绑定的部分:

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")); 
}

由于每个通道都有不同的设置对象,因此需要将它们分别注入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;
    }
}

这种方法的优点是它允许您添加其他内容ChannelSettings而无需编辑现有类。如果需要的话,还可以注入一部分通道设置。但是,这也会使配置和使用起来变得更加复杂,每个新通道都需要一个新的options对象,一个新的调用Configure()以及修改的构造函数SlackNotificationService

3.使用命名选项

这将我们带到此后命名选项的焦点命名选项听起来很像-它们是具有唯一名称的强类型配置选项。这样,您就可以在需要使用它们时按名称检索它们。

使用命名选项,您可以拥有多个独立配置的强类型设置实例。这意味着我们可以继续使用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")); 
}

我们使用适当的配置部分(例如"SlackApi:DevChannel"分别配置每个通道,但我们也提供名称作为Configure<T>调用的第一个参数此名称使我们能够从使用服务中检索特定配置。

要使用这些命名的选项,你必须注入IOptionsSnapshot<T>不能 IOptions<T>进入SlackNotificationService这使您可以访问该IOptionsSnapshot<T>.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");
    }
}

这种方法的最大优点是,您无需创建任何新的类或方法来添加新的通道,只需配置一个新的命名SlackApiSettings选项对象。的构造函数SlackNotifictionService也保持不变。在缺点方面,它不是从明确SlackNotificationService的构造完全相同,其设置的对象是依赖。而且,您现在真正依赖于作用域IOptionsSnapshot<T>接口,因此,正如我之前所描述的那样,没有一种简单的方法来删除IOptions<>依赖关系

哪种方法最适合您,将取决于您的要求和总体偏好。选项1在许多方面都是最简单的,如果您不希望添加选项对象的任何额外实例,那么它可能是一个不错的选择。如果以后可能会添加其他实例,则选项2很方便,但是您可以控制它们的添加时间(因此可以根据需要更新使用者服务)。当您无法控制何时添加新选项时,选项3特别有用。例如,ASP.NET Core框架本身使用命名选项作为身份验证选项,其中可以使用核心框架不了解的新身份验证处理程序。

摘要

在这篇文章中,我概述了如何在ASP.NET Core中的选项模式下使用强类型设置。然后,我讨论了在ASP.NET Core DI容器中注册多个强类型设置实例的要求。我描述了实现此目的的三种可能方法:创建父设置对象,为每个设置创建单独的派生类或使用命名选项。命名的选项可以使用IOptionsSnapshot<T>接口使用Get(name)方法来检索

 

 

转发自:https://andrewlock.net/using-multiple-instances-of-strongly-typed-settings-with-named-options-in-net-core-2-x/