Web框架 --- .NET中的Options Pattern

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.OptionsMicrosoft.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();
相关推荐
青茶3606 分钟前
【js教程】如何用jq的js方法获取url链接上的参数值?
开发语言·前端·javascript
脩衜者21 分钟前
极其灵活且敏捷的WPF组态控件ConPipe 2026
前端·物联网·ui·wpf
喵叔哟25 分钟前
16.项目架构设计
后端·docker·容器·.net
Mike_jia26 分钟前
Dockge:轻量开源的 Docker 编排革命,让容器管理回归优雅
前端
GISer_Jing33 分钟前
前端GEO优化:AI时代的SEO新战场
前端·人工智能
没想好d35 分钟前
通用管理后台组件库-4-消息组件开发
前端
文艺理科生37 分钟前
Google A2UI 解读:当 AI 不再只是陪聊,而是开始画界面
前端·vue.js·人工智能
晴栀ay39 分钟前
React性能优化三剑客:useMemo、memo与useCallback
前端·javascript·react.js
JS_GGbond39 分钟前
JavaScript继承大冒险:从“原型江湖”到“class殿堂”
前端
XiaoYu200239 分钟前
第6章 Postgres数据库安装
前端·postgresql