ASP.NET Core - 选项系统之选项配置

本文深入剖析了ASP.NET Core中的选项系统,涵盖手动绑定、依赖注入配置、接口使用、验证机制和源码解析,教你如何高效管理配置并实现选项类的强类型绑定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 选项

上一篇讲完了.NET Core 下的配置系统,我们可以通过 IConfiguration 服务从各种来源的配置中读取到配置信息,但是每次要用的时候都通过 IConfiguration 读取配置文件会比较不方便,而且效率低。.NET Core 体系下提供了一个选项系统,该功能用于实现以强类型的方式对程序配置信息进行访问,并且可以将选项类注入到依赖注入容器中进行管理和使用。

在进行配置信息的强类型选项绑定的时候,需要一个相应的选项类,该类推荐按{Object}Options命名规则进行命名,有以下特点:

  • 必须非抽象类
  • 必须包含公共无参的构造函数
  • 类中需要与配置项进行绑定的属性必须拥有public 的 get、set 访问器,并且属性的命名必须与配置键一致,不区分大小写
  • 要确保配置项能够转换到其绑定的属性类型
  • 该类的字段不会被绑定

2. 选项配置方式

2.1 手动绑定

上一篇讲到了 IConfiguration 服务通过 ConfigurationBinder 类扩展了 Get 和 Bind 两个方法,这两个方法可以将配置信息绑定到选项类上。这种方式其实在上一篇配置系统中已经有提过了,这里再做一下演示:

首先在配置文件中添加以下节点:

"Blog": {
    "Title": "ASP.NET Core Options",
    "Content": "This is a blog about Options System in ASP.NET Core Framework.",
    "CreateTime": "2022-12-06"
  }

之后定义一个选项类:

public class BlogOptions
{
    public const string Blog = "Blog";

    public string Title { get; set; }

    public string Content { get; set; }

    public DateTime CreateTime { get; set; }
}

然后,在任何可以获取到 IConfiguration 服务的地方都可以通过 IConfiguration 服务进行绑定:

using OptionsSample.Options;
using System.Text.Json;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

var blog1 = new BlogOptions();
app.Configuration.GetSection(BlogOptions.Blog).Bind(blog1);
var blog2 = app.Configuration.GetSection(BlogOptions.Blog).Get<BlogOptions>();

Console.WriteLine(JsonSerializer.Serialize(blog1));
Console.WriteLine(JsonSerializer.Serialize(blog2));

app.Run();

在这里插入图片描述

这种方式依旧有些不方便,每次都要指定相应的节点,每次都要实时构建新的选项对象,虽然也有好处,能够监测到配置的修改,在应用运行中同步改变(如果相应的配置处理程序支持变更重载的话),但选项系统也能做到配置更改时更新选项。

2.2 依赖注入配置

2.2.1 配置文件节点转换选项

除了手动绑定的方式外,我们还可以在应用启动的时候读取相应的配置节点,配置成Options并同时注册到依赖注入容器中,由依赖注入容器管理其生命周期,并且多次使用。我们可以像注册其他的依赖注入关系一样,在入口文件中通过 IServiceCollection 的 Configure 扩展方法进行配置。

// 通过配置文件读取某一配置节点
builder.Services.Configure<BlogOptions>(builder.Configuration.GetSection(BlogOptions.Blog));

这里通过获取特定的配置节点,并将其配置为选项,之后就可以在任何能够进行依赖注入的地方使用注入的 BlogOptions 选项类了。

var blogOption = app.Services.GetRequiredService<IOptions<BlogOptions>>().Value;
Console.WriteLine(JsonSerializer.Serialize(blogOption));

这里使用到的 Configure 方法是 OptionsConfigurationServiceCollectionExtensions 类中的扩展方法,选项系统中有好几个同名的 Configure 方法,但是这些并不是同一个方法的重载,下面会详细讲到。

2.2.2 硬编码配置选项

除了从配置文件中读取配置节点转换为选项之外,我们也可以直接在代码中硬编码指定选项内容,并注入到容器之中。

//硬编码的方式设置配置信息,也可以在这里读取数据库信息
builder.Services.Configure<BlogOptions>(option =>
{
    option.Title = "test";
    option.Content = "test hard code option";
    option.CreateTime = DateTime.Now;
});

这种情况用得不多,在这种情况下我们可以进行额外的一些逻辑,例如从数据库中获取一些信息。这里的 Configure 方法是 OptionsServiceCollectionExtensions 扩展类中的方法。值得注意的是,在同时使用上面两个方法配置同一个选项类的情况下,硬编码配置的方式优先。

2.2.3 使用DI服务配置选项

在某些场景下,选项的配置需要比较复杂的逻辑,会依赖容器中的其他服务,我们还可以使用以下方式:

builder.Services.AddOptions<BlogOptions>()
    // 这里可以通过 Configure 方法指定需要的服务, IServiceProvider 只是一个示例
    .Configure<IServiceProvider>((option, service) => // 接收的的第一个参数选项类对象,后面依次是所注入的服务
    {
        // 通过注入的服务执行相应的逻辑

        option.Title = "test DI Configure";
        option.Content = "test DI Configure";
        option.CreateTime = DateTime.Now;
    });

这里的 Configure 方法,与上面的不一样,不再是 IServiceCollection 的扩展方法,而是 OptionsBuilder<TOptions> 类中的方法,AddOptions 扩展方法在 OptionsServiceCollectionExtensions 中,返回一个 OptionsBuilder<TOptions> 对象。该方法有多个重载,最多支持5个服务来配置选项:
在这里插入图片描述
当使用这种方式和上面的硬编码的方式同时配置同一个选项类的情况下,哪部分代码后执行,最后选项类的内容就以哪部分为准。

2.2.4 命名选项

在一些情况下,应用中是存在多份配置结构相同,但具体配置值不同的配置信息的,例如以下的情况:

"FirstBlog": {
   "Title": "ASP.NET Core Options",
   "Content": "This is a blog about Options System in ASP.NET Core Framework.",
   "CreateTime": "2022-12-06"
 },
 "SecondBlog": {
   "Title": "ASP.NET Core Configuration",
   "Content": "This is a blog about Configuration System in ASP.NET Core Framework.",
   "CreateTime": "2022-12-08"
 }

这种情况下,两个配置节点其实可以用同一个选项类接收,它们的结构是一样的。而命名选项就是为了应对这种情况,选项系统中允许为当前配置的选项命名,而选项系统区分同一个类型的不同选项也是根据名字进行区分的。事实上,.NET Core 中所有 Options 都是命名选项,当没有显式指定名字时(如上面讲到的默认配置方式),使用的名字默认是Options.DefaultName,即string.Empty。

builder.Services.Configure<BlogOptions>("First", builder.Configuration.GetSection("FirstBlog"));
builder.Services.Configure<BlogOptions>("Second", builder.Configuration.GetSection("SecondBlog"));

这里要说明的是,上面我们从依赖注入容器中解析 IOptions<TOptions> 接口,从而获取我们需要的选项值,但是使用命名选项的情况下是无法通过 IOptions<TOptions> 接口解析相应的选项的,必须通过 IOptionsMonitor<TOptions> 或者 IOptionsSnapshot<TOptions> 接口来解析。这三个接口的区别下面会重点讲。

var blog = app.Services.GetRequiredService<IOptions<BlogOptions>>().Value;
Console.WriteLine(JsonSerializer.Serialize(blog));
var firstBlog = app.Services.GetRequiredService<IOptionsMonitor<BlogOptions>>().Get("First");
Console.WriteLine(JsonSerializer.Serialize(firstBlog));
var secondBlog = app.Services.GetRequiredService<IOptionsMonitor<BlogOptions>>().Get("Second");
Console.WriteLine(JsonSerializer.Serialize(secondBlog));

在这里插入图片描述

2.2.5 后期配置

后期配置是指当我们通过前面的方法对一个选项类进行配置之后,可能还会因为其他业务逻辑需要对其中的配置信息进行修改,这时候我们可以通过后期配置对选项系统中已配置的选项内容进行替换。后期配置在所有的OptionsServiceCollectionExtensions.Configure 执行完成之后执行,即配置系统不管代码顺序,会先完成所有选项的配置,再执行后期配置。

builder.Services.Configure<BlogOptions>("First", builder.Configuration.GetSection("FirstBlog"));
builder.Services.PostConfigure<BlogOptions>("First", options =>
{
    options.Title = "Post Config";
});

var firstBlog = app.Services.GetRequiredService<IOptionsMonitor<BlogOptions>>().Get("First");
Console.WriteLine(JsonSerializer.Serialize(firstBlog));

在这里插入图片描述
通过 IOptionsMonitor<TOptions> 或者 IOptionsSnapshot<TOptions> 接口解析选项,在配置来源更改的情况下,相应的选项内容也会随之变化(两者的适用场景不同), 后期配置逻辑在选项的每一次更改之后都会执行。


参考文章:

ASP.NET Core 中的选项模式 | Microsoft Learn
选项模式 - .NET | Microsoft Learn
面向 .NET 库创建者的选项模式指南 - .NET | Microsoft Learn
理解ASP.NET Core - 选项(Options)


ASP.NET Core 系列总结:

目录:ASP.NET Core 系列总结
上一篇:ASP.NET Core — 配置系统之配置提供程序
下一篇:ASP.NET Core — 选项系统之选项配置

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值