目的:在Program 初始化时,希望可以读取 appsettings.json
配置文件的某个节点的内容,自动映射到自定义类中,以便其他消费者进行注入使用配置的属性和值;
步骤1
:例如 appsettings.json 配置如下:
csharp
{
"Settings": {
"ApiUrl": "<https://wms.api.example.com>",
"Code": "PROG001"
}
}
步骤2
:创建一个自定义类,ServiceOptions
csharp
public class ServiceOptions
{
public string ApiUrl { get; set; }
public string Code { get; set; }
}
该类的属性,必须和 appsettings.json 文件中,
Settings
节点的属性相同
步骤3
:在 Program.cs 中注册服务到 DI 容器
csharp
builder.Services.Configure<ServiceOptions>(builder.Configuration.GetSection("Settings"));
最后
:在需要使用的地方,进行自动注入;例如,当前在一个Controller 中去使用配置
csharp
public class SomeController : ControllerBase
{
private readonly ServiceOptions _serviceOptions;
public SomeController(ServiceOptions service)
{
_serviceOptions = service;
}
}
如果在Program.cs 中注册的自定义配置类,但是由于其他一些服务的注册生命周期和自定义配置类的不同(默认scoped),导致服务无法解析,出现注册失败的情况。
解决方法:把消费者类构造函数改为接收
IOptionsMonitor<T>
(或 IOptions<T>
)如下:
csharp
public class SomeController : ControllerBase
{
private readonly ServiceOptions _serviceOptions;
public SomeController(IOptionsMonitor<ServiceOptions> service)
{
_serviceOptions = service?.CurrentValue;
// 如果需要在配置变更时更新内部状态,可订阅:
service.OnChange(newOpts => {
// 更新 _options 或执行必要逻辑(注意并发)
_serviceOptions = newOpts;
});
}
}
or
csharp
public class SomeController : ControllerBase
{
private readonly ServiceOptions _serviceOptions;
public SomeController(IOptions<ServiceOptions> service)
{
_serviceOptions = service.Value;
}
}
- 无论使用IOptions,或者是 IOptionsMonitor,都是直接使用_serviceOptions.Code 来直接访问相关属性
- 使用优先级
• 如果配置在运行时需要动态更新并且希望服务能感知这些变化,优先使用IOptionsMonitor<T>
。原因:IOptionsMonitor<T>
提供 CurrentValue 并能通过 OnChange 订阅配置变更,适合运行期需要热更新的场景。
• 如果配置在启动时读取一次即可且不会变化,使用 IOptions 更简单一些。原因:IOptions<T>
在注入时返回一个固定的快照(startup value),适合只读一次的场景。
• 绝不要用 IOptionsSnapshot 注入到单例(它是 scoped,只能被 scoped/transient 消费)
另外一种使用情况
:如果想 在 Program.cs 初始化时,也需要在 Program.cs
中临时使用 appsettings.json 节点的一些属性的值。
步骤一:除了可以通过 builder.Configuration["Settings:ApiUrl"]
这种方式简单获取,还可以继续使用上面的方式,映射到自定义的类中去获取相关的属性和值,
获取方式一:在 Program.cs 中,配置
csharp
ServiceOptions options = builder.Configuration.GetSection("Settings").Get<ServiceOptions>();
通过 options.Code
这样便能获取到对应的配置项的值;
获取方式二:同样在Program.cs 中,配置
csharp
var options = new ServiceOptions();
builder.Configuration.GetSection("WcsSettings").Bind(wcsOptions);
和 获取方式一 同样的道理,都是直接通过 options.Code
获取到对应配置的值
最后一种使用情况
:如果在 Program.cs 初始化时,同样需要临时使用 appsettings.json 节点的一些属性配置值,同时也需要把自定义的 ServiceOptions
类进行DI的注入,以便其他消费者类进行使用的话,可以这样去做,更加高效;
在Program.cs 中,配置一
csharp
builder.Services.AddOptions<ServiceOptions>()
.Bind(builder.Configuration.GetSection("Settings"))
.ValidateDataAnnotations(); // 自动验证注解
var options = new ServiceOptions();
builder.Configuration.GetSection("Settings").Bind(options);
使用自动验证注解的方式,自定义类需要配置注解,Demo 示例如下:
如若缺少 ApiKey 将触发 [Required] 验证错误
public class AppSettings { [Required(ErrorMessage = "API Key is required")] public string ApiKey { get; set; } [Range(1, 100, ErrorMessage = "MaxRetries must be between 1 and 100")] public int MaxRetries { get; set; } }
自定义验证
csharp
builder.Services.AddOptions<ServiceOptions>()
.Bind(builder.Configuration.GetSection("Settings"))
.ValidateDataAnnotations() // 内置验证支持
.Validate(options => options.Timeout > 0, "Timeout 必须大于 0"); // 自定义验证
启动时,立即验证支持
csharp
builder.Services.AddOptions<ServiceOptions>()
.Bind(builder.Configuration.GetSection("Settings"))
.ValidateDataAnnotations()
.ValidateOnStart(); // 应用启动时立即验证配置
配置二
csharp
builder.Services.Configure<ServiceOptions>(builder.Configuration.GetSection("Settings"));
var options = new ServiceOptions();
builder.Configuration.GetSection("Settings").Bind(options);
这样子就实现了,即能在 Program.cs 中临时使用配置的属性和值,也能自动注入到DI容器中,给其他消费者类进行使用。
总结表格
特性 | services.Configure<T>() |
services.AddOptions<T>().Bind() |
---|---|---|
绑定配置 | ✅ 直接绑定 | ✅ 通过Bind 方法绑定 |
DI注册 | ✅ 注册IOptions<T> 等 |
✅ 相同 |
链式配置 | ❌ 不支持链式,只能单独调用 | ✅ 支持链式,可接续配置验证、后置处理等 |
验证支持 | 需要单独调用验证方法 | ✅ 可链式调用验证(如.ValidateDataAnnotations() ) |
启动验证 | 需要额外步骤(如调用ValidateOnStart ) |
✅ 可链式调用.ValidateOnStart() |
配置覆盖/调整 | 只能通过多次调用Configure (顺序影响结果) |
✅ 可通过多个.Configure 调用在绑定前后调整配置 |
推荐场景 | 简单绑定,无需额外配置 | 需要验证、复杂初始化或链式配置的场景 |
功能等价性与底层机制
- 底层一致性
两种方式最终都会调用相同的配置绑定逻辑。AddOptions<T>().Bind()
内部实际执行了Configure<TOptions>(config)
的绑定过程,因此基础绑定功能完全等价 - DI 注册效果相同
无论使用哪种方式,都会将IOptions<T>
、IOptionsSnapshot<T>
等接口注册到依赖注入容器,支持通过构造函数注入
结论
- 功能上等价 :两者在绑定配置部分没有区别,因为
Bind
内部调用了Configure
- 灵活性 :
AddOptions<T>
提供了更流畅的API,便于添加验证和自定义配置,因此对于需要验证或复杂配置的场景,推荐使用AddOptions<T>().Bind()
的方式。 - 简洁性 :如果只是简单绑定,使用
Configure
更简洁。