使用.NET 6开发TodoList应用(26)——实现Configuration和Option的强类型绑定
系列导航及源代码
需求
在上一篇文章使用.NET 6开发TodoList应用(25)——实现RefreshToken中,我们通过使用Configuration获取方法GetSection
拿到写在appsettings.Development.json
中JWT的相关配置字段,这样实现没有问题,但是我们有更好的选择:通过使用强类型的Configuration绑定方法,或者通过Options相关方法来实现。在本文中我们将会分别来看一下这两种方法的实现。
目标
实现配置字段的强类型绑定,分别通过Configuration绑定和Options实现。
原理与思路
要实现强类型绑定,首先我们需要定义这个配置类型。然后根据需求,选择使用Configuration绑定实现或者使用Options配置实现。二者实现的功能上有一些区别:使用Options模式提供了更多的功能,如校验、热加载,也更方便进行测试。
实现
定义配置类型
根据我们在appsettings.Development.json
中的配置:
"JwtSettings": {
"validIssuer": "TodoListApi",
"validAudience": "https://localhost:5050",
"expires": 5
}
在Application/Configurations
中添加JwtConfiguration
类如下:
JwtConfiguration.cs
namespace TodoList.Application.Common.Configurations;
public class JwtConfiguration
{
public string Section { get; set; } = "JwtSettings";
public string? ValidIssuer { get; set; }
public string? ValidAudience { get; set; }
public string? Expires { get; set; }
}
方法1: 通过Configuration绑定实现
修改Infrastructure
项目中的DependencyInjection
添加认证方法的逻辑:
DependencyInjection.cs
// 省略其他...
// 添加认证方法为JWT Token认证
var jwtConfiguration = new JwtConfiguration();
configuration.Bind(jwtConfiguration.Section, jwtConfiguration);
services
.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
// 改为使用配置类成员获取
ValidIssuer = jwtConfiguration.ValidIssuer,
ValidAudience = jwtConfiguration.ValidAudience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("SECRET") ?? "TodoListApiSecretKey"))
};
});
修改IdentityService
中的逻辑,添加一个私有字段
IdentityService.cs
// 添加JWT配置类字段
private readonly JwtConfiguration _jwtConfiguration;
// 构造函数中进行初始化
// 初始化配置对象
_jwtConfiguration = new JwtConfiguration();
configuration.Bind(_jwtConfiguration.Section, _jwtConfiguration);
并将所有之前使用json对象获取字段值的地方都修改成通过私有字段的成员变量获取:
// 省略其他...
ValidIssuer = _jwtConfiguration.ValidIssuer,
ValidAudience = _jwtConfiguration.ValidAudience
验证如下,我们还是通过获取refresh token来检查配置是否成功:
看起来没什么问题。下面我们看第二种方法,也是相对比较推荐的做法。
方法2: 通过IOptions配置实现
我们在Infrastructure/DependencyInjection.cs
中添加使用IOptions的配置:
DependencyInjection.cs
// 使用IOptions配置
services.Configure<JwtConfiguration>(configuration.GetSection("JwtSettings"));
然后通过依赖注入的方式去修改IdentityService
:
IdentityService.cs
public IdentityService(
ILogger<IdentityService> logger,
IConfiguration configuration,
UserManager<ApplicationUser> userManager,
IOptions<JwtConfiguration> jwtOptions)
{
_logger = logger;
_userManager = userManager;
// 初始化配置对象
_jwtConfiguration = jwtOptions.Value;
}
其他的不需要再进行修改。下面来验证一下效果,验证方法和刚才一致:
可以看到依然是没有问题的。
一点扩展
扩展1: 关于配置热加载
综合我们刚才提到的和所演示的可以看到,我们并没有演示关于配置热加载的功能,如果在程序的运行过程中,我们希望配置文件的改动能够直接反映到应用中,不需要重启应用,可以预想到这个功能还是很重要的。
这个功能是通过IOptionsSnapshot
或者IOptionsMonitor
来实现的,我们所需要做的就是在依赖注入的时候使用IOptionsSnapshot<JwtConfiguration>
或者IOptionsMonitor<JwtConfiguration>
代替我们之前使用的IOptions<JwtConfiguration>
。在替换的过程中之需要注意以下两点即可:
IOptionsSnapshot
本身是注册为ScopedService
,所以不能注入到SingletonService
中使用;IOptionsMonitor
本身注册为了SingletonService
,所以可以注入到SingletonService
中使用,但是在取值的时候不是使用Value
而是使用CurrentValue
。
我们使用IOptionsMonitor
来举例子验证,只需要修改IdentityService
中构造函数的注入部分:
IdentityService.cs
public IdentityService(
ILogger<IdentityService> logger,
UserManager<ApplicationUser> userManager,
IOptionsMonitor<JwtConfiguration> jwtOptions)
{
_logger = logger;
_userManager = userManager;
// 使用IOptionsMonitor加载配置
_jwtConfiguration = jwtOptions.CurrentValue;
}
重新运行项目,我们先直接请求Token:
解析出的payload如下,过期时间是之前设置的5分钟后:
在不重启应用的情况下,我们去修改appsettings.Development.json
中关于过期时间的配置,将过期时间设置为10分钟:
"JwtSettings": {
"validIssuer": "TodoListApi",
"validAudience": "https://localhost:5050",
"expires": 10
}
再次执行获取Token的请求,查看Header里的Date字段值:
并把token解析:
这个过期时间已经变成10分钟后了,大家可以自己动手试一下。
扩展2: 关于相同类型的多个配置Section处理
有一种情况是在appsettings.Development.json
中我们可能会做这样的配置:
"JwtSettings": {
"validIssuer": "TodoListApi",
"validAudience": "https://localhost:5050",
"expires": 5
},
"JwtApiV2Settings": {
"validIssuer": "TodoListApiV2",
"validAudience": "https://localhost:5050",
"expires": 10
}
面对这种情况,我们可以在进行IOptions配置时指定配置名称,像这样:
// 使用IOptions配置
services.Configure<JwtConfiguration>("JwtSettings", configuration.GetSection("JwtSettings"));
services.Configure<JwtConfiguration>("JwtApiV2Settings", configuration.GetSection("JwtApiV2Settings"));
而在需要注入使用的地方也指定对应要进行配置的名称即可:
// 使用IOptionsMonitor加载配置
_jwtConfiguration = jwtOptions.Get("JwtApiV2Settings");
这样就可以正确地使用相应的配置了,就不再继续演示了。
总结
关于三种IOptions的对比见下表:
类型 | 依赖注入类型 | 是否支持配置热加载 | 配置加载更新时机 | 是否支持名称配置 |
---|---|---|---|---|
IOptions<T> |
Singleton注入 | 否 | 只在程序运行开始时绑定一次,以后每次获取的都是相同值 | 否 |
IOptionsSnapshot<T> |
Scoped注入 | 是 | 每次请求时都会重新加载配置 | 是 |
IOptionsMonitor<T> |
Singleton注入 | 是 | 配置的值被缓存起来了,当原始配置发生变化时立即发生更新 | 是 |
在本文中我们介绍了如何使用强类型绑定配置项,以及如何实现配置的热加载。对于没有涉及到的诸如配置项的校验等内容(可以通过Annotation实现校验)可以参考官方文档:Options validation
参考资料
使用.NET 6开发TodoList应用(26)——实现Configuration和Option的强类型绑定的更多相关文章
- 使用.NET 6开发TodoList应用(3)——引入第三方日志库
需求 在我们项目开发的过程中,使用.NET 6自带的日志系统有时是不能满足实际需求的,比如有的时候我们需要将日志输出到第三方平台上,最典型的应用就是在各种云平台上,为了集中管理日志和查询日志,通常会选 ...
- 使用.NET 6开发TodoList应用(1)——系列背景
前言 想到要写这样一个系列博客,初衷有两个:一是希望通过一个实践项目,将.NET 6 WebAPI开发的基础知识串联起来,帮助那些想要入门.NET 6服务端开发的朋友们快速上手,对使用.NET 6开发 ...
- 使用.NET 6开发TodoList应用(2)——项目结构搭建
为了不影响阅读的体验,我把系列导航放到文章最后了,有需要的小伙伴可以直接通过导航跳转到对应的文章 : P TodoList需求简介 首先明确一下我们即将开发的这个TodoList应用都需要完成什么功能 ...
- 使用.NET 6开发TodoList应用(4)——引入数据存储
需求 作为后端CRUD程序员(bushi,数据存储是开发后端服务一个非常重要的组件.对我们的TodoList项目来说,自然也需要配置数据存储.目前的需求很简单: 需要能持久化TodoList对象并对其 ...
- 使用.NET 6开发TodoList应用(5)——领域实体创建
需求 上一篇文章中我们完成了数据存储服务的接入,从这一篇开始将正式进入业务逻辑部分的开发. 首先要定义和解决的问题是,根据TodoList项目的需求,我们应该设计怎样的数据实体,如何去进行操作? 长文 ...
- 使用.NET 6开发TodoList应用(5.1)——实现Repository模式
需求 经常写CRUD程序的小伙伴们可能都经历过定义很多Repository接口,分别做对应的实现,依赖注入并使用的场景.有的时候会发现,很多分散的XXXXRepository的逻辑都是基本一致的,于是 ...
- 使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求
需求 需求很简单:如何创建新的TodoList和TodoItem并持久化. 初学者按照教程去实现的话,应该分成以下几步:创建Controller并实现POST方法:实用传入的请求参数new一个数据库实 ...
- 使用.NET 6开发TodoList应用文章索引
系列导航 使用.NET 6开发TodoList应用(1)--系列背景 使用.NET 6开发TodoList应用(2)--项目结构搭建 使用.NET 6开发TodoList应用(3)--引入第三方日志 ...
- 使用.NET 6开发TodoList应用(7)——使用AutoMapper实现GET请求
系列导航 使用.NET 6开发TodoList应用文章索引 需求 需求很简单:实现GET请求获取业务数据.在这个阶段我们经常使用的类库是AutoMapper. 目标 合理组织并使用AutoMapper ...
随机推荐
- 删除其他列Table.SelectColumns(Power Query 之 M 语言)
数据源: "姓名""基数""个人比例""个人缴纳""公司比例""公司缴纳"&qu ...
- 误入 GitHub 游戏区,意外地收获颇丰
这天中午,我和往常一样就着美食视频吃完午饭,然后起身泡了一杯"高沫". 我闻着茶香享受着午后的阳光,慵懒地坐在工位上习惯性的打开 GitHub 游荡,酝酿着睡意. 误打误撞,我来到 ...
- CF151B Phone Numbers 题解
Content 在一座城市中,每个人的电话号码都是由六位整数组成的,例如 11-45-14. 现在有 \(n\) 个人,第 \(i\) 个人有 \(s_i\) 个人的电话号码.已知: 出租车司机的电话 ...
- django报错TypeError at /items/join(), no dict
今天写Django模板的时候突然发现报了这个错误.stackflow了一下.改了一种dict的表达模式后成功解决. 错误: 报错写法: context = {'blogs': Blog.objects ...
- JAVA结合 JSON Web Token(JWT) 工具类
引入java-jwt-3.3.0.jar . jjwt-0.9.0.jar .jackson-all-1.7.6.jar 或者maven <!-- https://mvnrepository. ...
- 【LeetCode】191. Number of 1 Bits 解题报告(Java & Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 右移32次 计算末尾的1的个数 转成二进制统计1的个 ...
- 【LeetCode】304. Range Sum Query 2D - Immutable 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 预先求和 相似题目 参考资料 日期 题目地址:htt ...
- 【LeetCode】787. Cheapest Flights Within K Stops 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:DFS 方法二:BFS 参考资料 日期 题目 ...
- 【LeetCode】682. Baseball Game 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 使用栈模拟 日期 题目地址:https://leet ...
- 【LeetCode】39. Combination Sum 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:递归 方法二:回溯法 日期 题目地址:[htt ...