Ultimate ASP.NET CORE 6.0 Web API --- 读书笔记(29)

29 Binding Configuration And Options Pattern

本文内容来自书籍: Marinko Spasojevic - Ultimate ASP.NET Core Web API - From Zero To Six-Figure Backend Developer (2nd edition)

在之前的章节中,我们的JWT的配置是放在appsetting.json这个文件中的,然后需要读取的时候,会使用IConfiguration接口的方法

var jwtSettings = configuration.GetSection("JwtSettings");

ValidIssuer = jwtSettings["validIssuer"]

当然,这种方法可以很好的正常工作,但是,在获取方式上,我们必须输入key才能获取到值,而这样是很容易出错并且是在重复地编写类似的代码的。然后冒着风险引入错误到代码中,这会消耗我们大量的时间区排错,而且只有当读取这个值的时候,才会发生这种错误

所以,为了避免这种问题,我们会将配置数据绑定到一个强类型的对象上。

29.1 Binding Configuration

首先,在Entities中创建ConfigurationModels/JwtConfiguration

public class JwtConfiguration
{
    public string Section { get; set; } = "JwtSettings";

    public string? ValidIssuer { get; set; }

    public string? ValidAudience { get; set; }

    public string? Expires { get; set; }
}

然后,在读取JWT配置的地方,修改读取的方式

public static void ConfigureJWT(this IServiceCollection services, IConfiguration configuration)
{
    var jwtConfiguration = new JwtConfiguration();
    configuration.Bind(jwtConfiguration.Section, jwtConfiguration);
    
    var secretKey = Environment.GetEnvironmentVariable("SECRET") ?? "asdnAAjhaskdijhciwn";
    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(secretKey))
            };
        });
}

29.2 Options Pattern

前面展示的将配置参数绑定到一个强类型对象上,而Options Pattern也可以提供类似的功能,而且还能提供更多的结构化方法和更多特性,比如验证、实时重载,而且更加容易测试

一旦我们将配置类加载数据后,我们可以通过DI注入一个IOptions<T>到我们需要配置参数的地方

如果我们需要不停机重新加载配置文件,那我们可以使用IOptionsSnapshot<T>或者IOptionsMonitor<T>

还有就是它的验证机制是使用的DataAnotations attributes,也就是我们经常用来验证模型数据的注解

最后是它非常容易测试,因为它提供了helper方法,可以简单地mock一个option class

接下来我们开始使用这个IOptions接口

首先是,我们需要将前面的JwtConfiguration注册到服务里面

public static void AddJwtConfiguration(this IServiceCollection services, IConfiguration configuration) =>
        services.Configure<JwtConfiguration>(configuration.GetSection("JwtSettings"));


builder.Services.AddJwtConfiguration(builder.Configuration);

然后我们就可以使用了

public sealed class ServiceManager : IServiceManager
{
    private readonly IMapper _mapper;
    private readonly Lazy<ICompanyService> _companyService;
    private readonly Lazy<IEmployeeService> _employeeService;
    private readonly Lazy<IAuthenticationService> _authenticationService;

    public ServiceManager(IRepositoryManager repositoryManager, ILoggerManager logger, IMapper mapper,
        UserManager<User> userManager,
        IOptions<JwtConfiguration> configuration)
    {
        _mapper = mapper;
        _companyService = new Lazy<ICompanyService>(() => new CompanyService(repositoryManager, logger, mapper));
        _employeeService = new Lazy<IEmployeeService>(() => new EmployeeService(repositoryManager, logger, mapper));
        _authenticationService =
            new Lazy<IAuthenticationService>(
                () => new AuthenticationService(logger, mapper, userManager, configuration));
    }

    public ICompanyService CompanyService => _companyService.Value;

    public IEmployeeService EmployeeService => _employeeService.Value;

    public IAuthenticationService AuthenticationService => _authenticationService.Value;
}

internal sealed class AuthenticationService : IAuthenticationService
{
    private readonly ILoggerManager _logger;
    private readonly IMapper _mapper;
    private readonly UserManager<User> _userManager;
    private readonly IOptions<JwtConfiguration> _configuration;
    private readonly JwtConfiguration _jwtConfiguration;

    private User? _user;

    public AuthenticationService(ILoggerManager logger, IMapper mapper,
        UserManager<User> userManager, IOptions<JwtConfiguration> configuration)
    {
        _logger = logger;
        _mapper = mapper;
        _userManager = userManager;
        _configuration = configuration;
        _jwtConfiguration = _configuration.Value;
    }
}

如果这个配置,需要在某些时候进行修改,但是有不希望停机的时候,那么可以使用不一样的两种接口来实现

代码上,只需要将IOptions<JwtConfiguration>替换为IOptionsSnapshot<JwtConfiguration>或者IOptionsMonitor<JwtConfiguration>

而这两种接口的区别是:IOptionsSnapshot这个接口会被注册为scoped的服务,所以它不可以被注入到singleton类型的服务;而IOptionsMonitor是会被注册为singleton服务,它可以被注入到任意生命周期的服务

为了更加清晰的接口间的比较,下面列出来

IOptions:

  • Options的原始接口,比绑定整个配置更加友好
  • 不支持配置热更新
  • 注册为singleton服务,配置参数的绑定只会发生一次
  • 不支持命名选项

IOptionsSnapshot:

  • 注册为scoped服务
  • 支持配置热更新
  • 不能注入到singleton的服务
  • 配置参数会在每次请求都重新读取
  • 支持命名选项

IOptionsMonitor:

  • 注册为singleton服务
  • 支持配置热更新
  • 可以注入到任意生命周期的服务
  • 配置参数会被缓存而且会马上重新加载
  • 支持命名选项

前面提到几次Named Options,现在解释以下是什么意思,假设现在有配置文件如下

"JwtSettings": {
    "validIssuer": "CodeMazeAPI",
    "validAudience": "https://localhost:5001",
    "expires": 5
},
"JwtAPI2Settings": {
    "validIssuer": "CodeMazeAPI2",
    "validAudience": "https://localhost:5002",
    "expires": 10
}

然后我们增加一个新的类接收新的配置,虽然字段全都一样

services.Configure<JwtConfiguration>("JwtSettings", configuration.GetSection("JwtSettings"));
services.Configure<JwtConfiguration>("JwtAPI2Settings", configuration.GetSection("JwtAPI2Settings"));

字段都一样,只是key不一样,其实我们可以用同一个类来接收,只是名字不一样就可以了

这样,我们在获取配置的时候,就不使用Value方法,而是用Get方法

_jwtConfiguration = _configuration.Get("JwtSettings");
posted @   huang1993  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示