.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 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.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处理多个强类型配置实例的更多相关文章
- web工程中web.xml元素加载顺序以及配置实例
简介 web.xml是web工程的配置文件,容器加载web工程时,会首先从WEB-INF中查询web.xml,并加载其中的配置信息,可以将web.xml认为是web工程的入口. web.xml中包含有 ...
- 如何为ASP.NET Core的强类型配置对象添加验证
原文: Adding validation to strongly typed configuration objects in ASP.NET Core 作者: Andrew Lock 译文: La ...
- ASP.NET Core 选项模式源码学习Options Configure(一)
前言 ASP.NET Core 后我们的配置变得更加轻量级了,在ASP.NET Core中,配置模型得到了显著的扩展和增强,应用程序配置可以存储在多环境变量配置中,appsettings.json用户 ...
- 避免在ASP.NET Core 3.0中为启动类注入服务
本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingE ...
- ASP.NET Core 1.0 中使用 Swagger 生成文档
github:https://github.com/domaindrivendev/Ahoy 之前文章有介绍在ASP.NET WebAPI 中使用Swagger生成文档,ASP.NET Core 1. ...
- Entity Framework Core 2.0 中使用LIKE 操作符
Entity Framework Core 2.0 中使用LIKE 操作符 不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译 ...
- 在ASP.NET Core 2.0中使用CookieAuthentication
在ASP.NET Core中关于Security有两个容易混淆的概念一个是Authentication(认证),一个是Authorization(授权).而前者是确定用户是谁的过程,后者是围绕着他们允 ...
- ASP.NET Core 2.1中基于角色的授权
ASP.NET Core 2.1中基于角色的授权 授权是来描述用户能够做什么的过程.例如,只允许管理员用户可以在电脑上进行软件的安装以及卸载.而非管理员用户只能使用软件而不能进行软件的安装以及卸载.它 ...
- ASP.NET Core 2.2中的Endpoint路由
Endpoint路由 在ASP.NET Core 2.2中,新增了一种路由,叫做Endpoint(终结点)路由.本文将以往的路由系统称为传统路由. 本文通过源码的方式介绍传统路由和Endpoint路由 ...
随机推荐
- python tesserocr ImportError: dll loading failed 一个不常遇见的错误,以及简单的python安装方法~
废话不多说了,这是写给小白的了. 本人有c#,c/c++开发经验,最近因为偶然的原因,开始接触python,遇到一个棘手的问题,一位朋友在安装tesserocr 包后遇到一个错误,重新安装数次不能解决 ...
- Windows phone 8.1之数据绑定(Data Binding)
学习Winphone8.1的时候经常需要对Slider进行数据绑定后使之视觉化,方便调节Slider的值. 数据绑定分为源(Source)和目标(Target),Source一般分为两种,其他控件的数 ...
- Linux学习之基本操作命令
目录基本操作命令 列目录内容ls ls [options] [files] #options是可选参数 常用可选参数:-a 所有文件及目录 -A 等同于-a,但是不列出.以及.. -l 长格 ...
- 创建线程的一般方式和匿名内部类方式对比——继承thread类,重写run()方法
第一种:继承thread类,重写run()方法 一般方式:Demo01.java /** * 创建线程的第一种方式:继承thread类,重写run()方法 * * @author :liuqi * @ ...
- 无线路由WMM,Short GI,AP隔离功能介绍(转)
无线路由器中有开启WMM.开启Short GI和开启AP隔离分别代表什么呢?这是我在我的TP-LINK无线路由器TL-WR841N中的无线高级设置中看到的三个选项,下面三点对这三项无线高级设置做了下解 ...
- PPT vba从Execl 拷贝图表
在PPT 需要引用Execl的COM组件 Dim wkb As Workbook Sub Change() Set wkb = Workbooks.Open("D:\D2_月报基础数据.xl ...
- Gcd&Exgcd算法学习小记
Preface 对于许多数论问题,都需要涉及到Gcd,求解Gcd,常常使用欧几里得算法,以前也只是背下来,没有真正了解并证明过. 对于许多求解问题,可以列出贝祖方程:ax+by=Gcd(a,b),用E ...
- 神奇高效的Linux命令行
一.为什么要学linux命令 Linux是由命令行组成的操作系统,精髓在命令行,无论图形界面发展到什么水平,命令行方式的操作永远是不会变的.Linux命令有许多强大的功能:从简单的磁盘操作.文件存取, ...
- vue的环境安装(二)
1.安装淘宝镜像 打开命令行,输入以下命令:npm install -g cnpm --registry= https://registry.npm.taobao.org2.全局安装 vue- ...
- 三、糖醋鲤鱼(Sweet and sour carp)
糖醋鲤鱼是用鲤鱼制作的一道山东济南传统名菜,为鲁菜的代表菜品之一 ,色泽金黄,外焦内嫩,酸甜可口,香鲜味美. 菜品历史 <诗经>载:岂食其鱼,必河之鲤.<济南府志>上早有&qu ...