Web框架 --- .NET中的Options Pattern
- [一、什么是 Options Pattern?](#一、什么是 Options Pattern?)
- 二、核心优势
- 三、核心组件
-
- [1. 强类型 Options 类](#1. 强类型 Options 类)
- [2. 选项访问接口](#2. 选项访问接口)
- [3. 配置绑定与注册服务](#3. 配置绑定与注册服务)
- [四、使用步骤(以 ASP.NET Core 为例)](#四、使用步骤(以 ASP.NET Core 为例))
-
- [步骤 1:准备配置源(appsettings.json)](#步骤 1:准备配置源(appsettings.json))
- [步骤 2:定义强类型 Options 类](#步骤 2:定义强类型 Options 类)
- [步骤 3:注册 Options 服务(Program.cs)](#步骤 3:注册 Options 服务(Program.cs))
- [步骤 4:在业务逻辑中使用 Options](#步骤 4:在业务逻辑中使用 Options)
- [步骤 5:控制器中使用(可选)](#步骤 5:控制器中使用(可选))
- 五、高级特性示例
-
- [1. 配置变更监听(IOptionsMonitor<T>)](#1. 配置变更监听(IOptionsMonitor<T>))
- [2. 命名选项(多实例配置)](#2. 命名选项(多实例配置))
- 六、注意事项
- 七、控制台项目示例(补充)
一、什么是 Options Pattern?
Options Pattern(选项模式)是 .NET 框架中用于强类型配置管理 的官方推荐设计模式。它的核心作用是将分散在配置源(如 appsettings.json、环境变量、命令行参数、Azure Key Vault 等)中的配置数据,映射到自定义的强类型类(Options 类)中,从而替代传统的字符串索引式访问(如 Configuration["Key"]),让配置管理更规范、可维护、可测试。
二、核心优势
-
强类型校验:编译期即可发现配置键名错误,避免运行时因字符串写错导致的问题;
-
依赖注入友好:可通过 .NET 内置 DI 容器直接注入 Options 实例,无需手动读取配置;
-
支持高级特性:内置配置验证、配置变更监听、命名选项(多实例配置)等功能;
-
分离关注点:配置结构与业务逻辑分离,便于统一管理和修改。
三、核心组件
Options Pattern 依赖 Microsoft.Extensions.Options 组件(默认已包含在 ASP.NET Core 项目模板中,控制台项目需手动安装),核心组件分为三类:
1. 强类型 Options 类
用于定义配置结构的 POCO 类(Plain Old CLR Object,简单老式 CLR 对象),需满足:
-
公开无参构造函数(便于框架反射实例化);
-
属性通常为自动属性(get/set),名称与配置源中的键名一致(大小写不敏感);
-
可通过数据注解添加配置验证规则。
2. 选项访问接口
用于从 DI 容器获取配置实例,核心接口有 3 个,适用于不同场景:
| 接口 | 生命周期 | 是否感知配置变更 | 适用场景 |
|---|---|---|---|
| IOptions | 单例(Singleton) | 否 | 配置不动态变更的场景(如数据库连接串、第三方 API 基础地址) |
| IOptionsSnapshot | 作用域(Scoped) | 是(每次请求/作用域重新读取) | Web 应用中需要感知配置变更的场景(如动态开关、限流阈值) |
| IOptionsMonitor | 单例(Singleton) | 是(实时监听变更,支持回调) | 控制台/Windows 服务等长驻进程,需要实时响应配置变更的场景 |
3. 配置绑定与注册服务
通过 IServiceCollection 的扩展方法注册 Options 服务,核心方法:
-
AddOptions():基础注册(ASP.NET Core 已自动调用,无需手动写); -
Configure<TOptions>(IConfiguration configuration):将配置源中的指定节点绑定到 TOptions 类; -
Configure<TOptions>(Action<TOptions> configureOptions):通过代码硬编码配置 TOptions; -
ValidateDataAnnotations():启用数据注解验证; -
Validate(Func<TOptions, bool> validation, string message):自定义验证规则。
四、使用步骤(以 ASP.NET Core 为例)
步骤 1:准备配置源(appsettings.json)
在 appsettings.json 中添加自定义配置节点(示例:数据库配置 + 第三方 API 配置):
json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
// 自定义数据库配置节点
"DatabaseOptions": {
"ConnectionString": "Server=localhost;Database=DemoDB;Uid=sa;Pwd=123456;",
"CommandTimeout": 30,
"EnableCache": true
},
// 自定义第三方API配置节点
"ApiOptions": {
"BaseUrl": "https://api.demo.com",
"Timeout": 5,
"ApiKey": "abc123456789"
}
}
步骤 2:定义强类型 Options 类
创建与配置节点对应的 POCO 类,可添加数据注解验证:
csharp
using System.ComponentModel.DataAnnotations;
// 数据库配置选项类
public class DatabaseOptions
{
// 数据注解验证:必填项
[Required(ErrorMessage = "数据库连接字符串不能为空")]
public string ConnectionString { get; set; } = string.Empty;
// 数据注解验证:最小值限制
[Range(1, 60, ErrorMessage = "命令超时时间必须在 1-60 秒之间")]
public int CommandTimeout { get; set; }
public bool EnableCache { get; set; }
}
// 第三方API配置选项类
public class ApiOptions
{
[Required(ErrorMessage = "API基础地址不能为空")]
[Url(ErrorMessage = "API基础地址格式不正确")]
public string BaseUrl { get; set; } = string.Empty;
[Range(1, 30, ErrorMessage = "请求超时时间必须在 1-30 秒之间")]
public int Timeout { get; set; }
[Required(ErrorMessage = "API密钥不能为空")]
public string ApiKey { get; set; } = string.Empty;
}
步骤 3:注册 Options 服务(Program.cs)
在 Program.cs 中通过 Configure<TOptions> 绑定配置,并启用验证:
csharp
var builder = WebApplication.CreateBuilder(args);
// 1. 绑定 DatabaseOptions:将 appsettings.json 中的 DatabaseOptions 节点绑定到 DatabaseOptions 类
builder.Services.Configure<DatabaseOptions>(
builder.Configuration.GetSection("DatabaseOptions"))
.ValidateDataAnnotations(); // 启用数据注解验证
// 2. 绑定 ApiOptions:将 appsettings.json 中的 ApiOptions 节点绑定到 ApiOptions 类
builder.Services.Configure<ApiOptions>(
builder.Configuration.GetSection("ApiOptions"))
.ValidateDataAnnotations();
// 3. 注册业务服务(示例:使用 Options 的服务)
builder.Services.AddScoped<IDbService, DbService>();
builder.Services.AddScoped<IApiService, ApiService>();
// 其他服务注册(如控制器、视图等)
builder.Services.AddControllersWithViews();
var app = builder.Build();
// 省略中间件配置...
app.Run();
步骤 4:在业务逻辑中使用 Options
通过构造函数注入选项访问接口(IOptions<T> / IOptionsSnapshot<T> / IOptionsMonitor<T>),获取配置实例:
csharp
// 数据库业务服务
public interface IDbService
{
string GetConnectionString();
}
public class DbService : IDbService
{
private readonly DatabaseOptions _dbOptions;
// 注入 IOptions<DatabaseOptions>(配置不动态变更,用单例接口)
public DbService(IOptions<DatabaseOptions> dbOptions)
{
// 注意:通过 Value 属性获取实际的配置实例
_dbOptions = dbOptions.Value;
}
public string GetConnectionString()
{
return _dbOptions.ConnectionString;
}
}
// API 业务服务
public interface IApiService
{
Task<string> CallApiAsync();
}
public class ApiService : IApiService
{
private readonly ApiOptions _apiOptions;
private readonly HttpClient _httpClient;
// 注入 IOptionsSnapshot<ApiOptions>(需要感知配置变更,用作用域接口)
public ApiService(IOptionsSnapshot<ApiOptions> apiOptions, HttpClient httpClient)
{
_apiOptions = apiOptions.Value;
_httpClient = httpClient;
}
public async Task<string> CallApiAsync()
{
// 使用配置中的基础地址、超时时间、API密钥
_httpClient.BaseAddress = new Uri(_apiOptions.BaseUrl);
_httpClient.Timeout = TimeSpan.FromSeconds(_apiOptions.Timeout);
_httpClient.DefaultRequestHeaders.Add("ApiKey", _apiOptions.ApiKey);
var response = await _httpClient.GetAsync("data");
return await response.Content.ReadAsStringAsync();
}
}
步骤 5:控制器中使用(可选)
直接在控制器中注入选项,供接口调用使用:
csharp
public class HomeController : Controller
{
private readonly IDbService _dbService;
private readonly IApiService _apiService;
public HomeController(IDbService dbService, IApiService apiService)
{
_dbService = dbService;
_apiService = apiService;
}
public IActionResult Index()
{
var connStr = _dbService.GetConnectionString();
ViewBag.ConnStr = connStr;
return View();
}
public async Task<IActionResult> CallApi()
{
var result = await _apiService.CallApiAsync();
ViewBag.ApiResult = result;
return View();
}
}
五、高级特性示例
1. 配置变更监听(IOptionsMonitor)
对于长驻进程(如控制台服务),使用IOptionsMonitor<T> 实时监听配置变更:
csharp
public class MonitorService
{
private readonly IOptionsMonitor<ApiOptions> _apiOptionsMonitor;
private IDisposable? _changeToken;
public MonitorService(IOptionsMonitor<ApiOptions> apiOptionsMonitor)
{
_apiOptionsMonitor = apiOptionsMonitor;
// 监听配置变更
_changeToken = _apiOptionsMonitor.OnChange((newOptions, name) =>
{
Console.WriteLine($"ApiOptions 配置变更!新的 BaseUrl:{newOptions.BaseUrl}");
});
}
// 释放监听资源
public void Dispose()
{
_changeToken?.Dispose();
}
}
2. 命名选项(多实例配置)
当需要同一配置结构的多个实例(如多个数据库连接)时,使用命名选项:
json
// appsettings.json 中添加多个数据库节点
"Databases": {
"PrimaryDb": {
"ConnectionString": "Server=localhost;Database=PrimaryDB;Uid=sa;Pwd=123456;",
"CommandTimeout": 30
},
"SecondaryDb": {
"ConnectionString": "Server=localhost;Database=SecondaryDB;Uid=sa;Pwd=123456;",
"CommandTimeout": 30
}
}
csharp
// Program.cs 注册命名选项
builder.Services.Configure<DatabaseOptions>("PrimaryDb",
builder.Configuration.GetSection("Databases:PrimaryDb"));
builder.Services.Configure<DatabaseOptions>("SecondaryDb",
builder.Configuration.GetSection("Databases:SecondaryDb"));
// 使用命名选项(通过 IOptionsMonitor<T> 获取指定名称的实例)
public class MultiDbService
{
private readonly DatabaseOptions _primaryDb;
private readonly DatabaseOptions _secondaryDb;
public MultiDbService(IOptionsMonitor<DatabaseOptions> monitor)
{
// 通过 Get 方法获取指定名称的选项
_primaryDb = monitor.Get("PrimaryDb");
_secondaryDb = monitor.Get("SecondaryDb");
}
}
六、注意事项
-
Options 类必须是 POCO 类,不能是抽象类或接口,且需有无参构造函数;
-
配置绑定是"大小写不敏感"的,但建议配置键名与类属性名保持一致,提高可读性;
-
IOptions 是单例,首次访问后不会再读取配置,适合静态配置;若需动态变更,优先使用 IOptionsSnapshot(Web 应用)或 IOptionsMonitor(长驻进程);
-
配置验证失败时,默认会在首次获取 Options.Value 时抛出异常,可通过
ValidateOnStart()方法改为程序启动时验证(提前暴露问题):
csharp
// 启动时验证配置(推荐)
builder.Services.Configure<DatabaseOptions>(builder.Configuration.GetSection("DatabaseOptions"))
.ValidateDataAnnotations()
.ValidateOnStart();
- 控制台项目使用 Options Pattern 时,需手动安装
Microsoft.Extensions.Options和Microsoft.Extensions.Configuration.Json包。
七、控制台项目示例(补充)
若在控制台项目中使用 Options Pattern,步骤如下:
csharp
// 1. 安装依赖包
// Install-Package Microsoft.Extensions.Options
// Install-Package Microsoft.Extensions.Configuration.Json
// Install-Package Microsoft.Extensions.DependencyInjection
// 2. Program.cs
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
var services = new ServiceCollection();
// 3. 构建配置
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
// 4. 注册 Options
services.Configure<DatabaseOptions>(configuration.GetSection("DatabaseOptions"))
.ValidateDataAnnotations()
.ValidateOnStart();
// 5. 注册业务服务
services.AddScoped<IDbService, DbService>();
// 6. 构建服务提供器
var serviceProvider = services.BuildServiceProvider();
// 7. 使用服务
var dbService = serviceProvider.GetRequiredService<IDbService>();
Console.WriteLine($"数据库连接串:{dbService.GetConnectionString()}");
Console.ReadKey();