在C#应用程序开发中,我们常常需要从配置文件里读取各类业务参数,比如数据库连接字符串、第三方接口地址、系统功能开关等。传统的配置读取方式往往直接通过字符串键获取值,不仅容易写错键名,还缺少类型校验,后期维护成本较高。Options模式是.NET Core及后续版本中官方推荐的类型安全配置方案,通过IOptions相关接口可以将配置节点直接映射为强类型对象,大幅降低配置读取的错误率。

Options模式核心概念
Options模式的核心是将配置文件中的一个节点映射为一个对应的C#类,这个类的属性名需要和配置节点的键名保持一致,这样框架就能自动完成值的绑定。相关的核心接口有三个,各自有不同的适用场景:
- IOptions<TOptions>:单例生命周期,配置值在应用启动时加载,后续不会随配置文件变化而更新,适合读取不会动态修改的配置。
- IOptionsSnapshot<TOptions>:作用域内生命周期,每次请求会重新读取配置,适合需要感知配置文件修改的场景,但仅能在作用域中使用。
- IOptionsMonitor<TOptions>:单例生命周期,支持监听配置文件变化,配置修改后会自动更新绑定的对象,适合需要实时感知配置变更的场景。
准备配置类和配置文件
首先我们需要定义一个和配置节点对应的强类型类,假设我们要读取一个名为AppSettings的配置节点,对应的类可以这样定义:
public class AppSettings
{
// 对应配置中的MaxRetryCount键
public int MaxRetryCount { get; set; }
// 对应配置中的ApiBaseUrl键
public string ApiBaseUrl { get; set; }
// 对应配置中的IsEnableLog键
public bool IsEnableLog { get; set; }
}
然后在appsettings.json配置文件中添加对应的配置节点,内容如下:
{
"AppSettings": {
"MaxRetryCount": 3,
"ApiBaseUrl": "https://api.ipipp.com/v1",
"IsEnableLog": true
}
}
注册Options服务并绑定配置
在.NET应用的启动阶段,我们需要在依赖注入容器中注册Options服务,同时完成配置和类的绑定,这一步通常在Program.cs中完成:
var builder = WebApplication.CreateBuilder(args);
// 注册Options服务,将AppSettings配置节点绑定到AppSettings类
builder.Services.Configure<AppSettings>(
builder.Configuration.GetSection("AppSettings")
);
var app = builder.Build();
// 其他中间件配置...
app.Run();
这里使用Configure方法完成绑定,第一个泛型参数是我们的配置类类型,参数传入的是配置节点对应的IConfigurationSection对象。
使用IOptions读取配置
注册完成后,我们就可以在控制器、服务等地方通过依赖注入获取对应的Options对象来读取配置了,以下是控制器的使用示例:
using Microsoft.Extensions.Options;
[ApiController]
[Route("api/[controller]")]
public class TestController : ControllerBase
{
private readonly AppSettings _appSettings;
// 通过构造函数注入IOptions<AppSettings>
public TestController(IOptions<AppSettings> options)
{
// 通过Value属性获取绑定的配置对象
_appSettings = options.Value;
}
[HttpGet("config")]
public IActionResult GetConfig()
{
return Ok(new
{
MaxRetryCount = _appSettings.MaxRetryCount,
ApiBaseUrl = _appSettings.ApiBaseUrl,
IsEnableLog = _appSettings.IsEnableLog
});
}
}
访问这个接口后,就能得到我们在配置文件中定义的三个配置值,这种方式读取的配置是强类型的,不需要手动做类型转换,也不容易写错键名。
不同Options接口的使用差异
如果我们希望配置修改后不需要重启应用就能生效,就需要根据场景选择不同的Options接口:
使用IOptionsSnapshot
IOptionsSnapshot适合在作用域生命周期中使用,比如控制器的默认生命周期就是作用域,修改appsettings.json后,下一次请求就会读取到新的配置值:
public class TestController : ControllerBase
{
private readonly AppSettings _appSettings;
// 注入IOptionsSnapshot<AppSettings>
public TestController(IOptionsSnapshot<AppSettings> options)
{
_appSettings = options.Value;
}
}
使用IOptionsMonitor
IOptionsMonitor支持配置变更监听,我们可以在配置变化时执行自定义逻辑,适合单例服务中需要感知配置变更的场景:
public class ConfigChangeService
{
public ConfigChangeService(IOptionsMonitor<AppSettings> options)
{
// 监听配置变化,变化时执行回调
options.OnChange(newConfig =>
{
Console.WriteLine($"配置已更新,新的ApiBaseUrl为:{newConfig.ApiBaseUrl}");
});
}
}
同时需要在Program.cs中注册这个服务:
builder.Services.AddScoped<ConfigChangeService>();
命名选项配置
如果我们有多个同类型的配置节点,比如多个不同环境的API配置,可以使用命名选项来区分,首先在配置文件中添加多个节点:
{
"AppSettings": {
"Dev": {
"MaxRetryCount": 1,
"ApiBaseUrl": "https://dev-api.ipipp.com/v1",
"IsEnableLog": true
},
"Prod": {
"MaxRetryCount": 3,
"ApiBaseUrl": "https://prod-api.ipipp.com/v1",
"IsEnableLog": false
}
}
}
注册的时候为不同的配置节点指定名称:
// 注册Dev环境的配置
builder.Services.Configure<AppSettings>("Dev",
builder.Configuration.GetSection("AppSettings:Dev"));
// 注册Prod环境的配置
builder.Services.Configure<AppSettings>("Prod",
builder.Configuration.GetSection("AppSettings:Prod"));
使用的时候通过名称获取对应的配置:
public class TestController : ControllerBase
{
private readonly AppSettings _devSettings;
private readonly AppSettings _prodSettings;
public TestController(IOptionsSnapshot<AppSettings> options)
{
// 根据名称获取对应的配置
_devSettings = options.Get("Dev");
_prodSettings = options.Get("Prod");
}
}
注意事项
- 配置类的属性必须是公共的且具有对应的setter,否则框架无法完成值绑定。
- 配置键名和类属性名默认是大小写不敏感的,但是如果配置了大小写敏感的配置源,就需要保持一致。
- IOptions不支持配置热更新,如果不需要动态更新配置,优先使用IOptions可以减少不必要的性能开销。
- 如果配置绑定失败,比如类型不匹配,应用启动时会直接抛出异常,方便我们提前发现问题。