来源: 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中的IOptionsSnapshotIOptionsSnapshotIOptions使用方法一样,因此你无需在应用程序中执行任何额外的操作。你只需要使用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

.NET Core 2.x中使用Named Options处理多个强类型配置实例的更多相关文章

  1. web工程中web.xml元素加载顺序以及配置实例

    简介 web.xml是web工程的配置文件,容器加载web工程时,会首先从WEB-INF中查询web.xml,并加载其中的配置信息,可以将web.xml认为是web工程的入口. web.xml中包含有 ...

  2. 如何为ASP.NET Core的强类型配置对象添加验证

    原文: Adding validation to strongly typed configuration objects in ASP.NET Core 作者: Andrew Lock 译文: La ...

  3. ASP.NET Core 选项模式源码学习Options Configure(一)

    前言 ASP.NET Core 后我们的配置变得更加轻量级了,在ASP.NET Core中,配置模型得到了显著的扩展和增强,应用程序配置可以存储在多环境变量配置中,appsettings.json用户 ...

  4. 避免在ASP.NET Core 3.0中为启动类注入服务

    本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingE ...

  5. ASP.NET Core 1.0 中使用 Swagger 生成文档

    github:https://github.com/domaindrivendev/Ahoy 之前文章有介绍在ASP.NET WebAPI 中使用Swagger生成文档,ASP.NET Core 1. ...

  6. Entity Framework Core 2.0 中使用LIKE 操作符

    Entity Framework Core 2.0 中使用LIKE 操作符 不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译 ...

  7. 在ASP.NET Core 2.0中使用CookieAuthentication

    在ASP.NET Core中关于Security有两个容易混淆的概念一个是Authentication(认证),一个是Authorization(授权).而前者是确定用户是谁的过程,后者是围绕着他们允 ...

  8. ASP.NET Core 2.1中基于角色的授权

    ASP.NET Core 2.1中基于角色的授权 授权是来描述用户能够做什么的过程.例如,只允许管理员用户可以在电脑上进行软件的安装以及卸载.而非管理员用户只能使用软件而不能进行软件的安装以及卸载.它 ...

  9. ASP.NET Core 2.2中的Endpoint路由

    Endpoint路由 在ASP.NET Core 2.2中,新增了一种路由,叫做Endpoint(终结点)路由.本文将以往的路由系统称为传统路由. 本文通过源码的方式介绍传统路由和Endpoint路由 ...

随机推荐

  1. Java 博客导航

    Java 博客导航 一.基础知识 Java 基础知识 Java 常用知识点 Java 多线程 Java 正则使用 Java IO Java 集合

  2. 【转】window.onerror跨域问题

    What the heck is "Script error"? Ben Vinegar/ May 17, 2016 If you’ve done any work with th ...

  3. java自动化-实际使用junit的演示

    本文简单介绍一下我写的http接口后端框架 在经过之前多篇博客介绍之后,读者应掌握如下技能 1,自动运行一个或者多个junit框架编写的java代码 2,对数据驱动以及关键字驱动有一定的了解和认识,甚 ...

  4. [Linux] 使用Yum在CentOS上安装MySQL

    跟随官网上的安装教程:https://dev.mysql.com/doc/refman/8.0/en/linux-installation-yum-repo.html官网上还有一个QuickGuide ...

  5. WebStorm重复代码快捷表达

    一,问题 平时使用WebStorm时需要很多引用js,重复代码比较多,每次都要写很多次同样的代码,那么如何通过几个快捷键来简单的写出重复代码呢? 问题具体描述: 每次都要写两个script的重复: & ...

  6. 原生JS制作简易Tabs组件

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. LOJ 6092

    这个题也很没意思 发现q那么大没有用, 不重复的询问有26*n种 所以记录一下就好了 #include<bits/stdc++.h> using namespace std; #defin ...

  8. input框中修改placeholder的样式

    有时间input标签的placeholder属性会出现问题,下面是修改placeholder的样式demo input::-webkit-input-placeholder{ color:red; f ...

  9. vue路由传参的三种方式区别(params,query)

    最近在做一个项目涉及到列表到详情页的参数的传递,网上搜索一下路由传参,结合自己的写法找到一种适合自己的,不过也对三种写法都有了了解,在此记录一下 <ul class="table_in ...

  10. 3、java面向对象编程

    1.面向对象内存分析 栈的特点 (1)JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数.局部变量等) (2)栈属于线程私有,不能实现线程间的共享! (3)栈的存储特性是:先进后出,后 ...