4. 配置系统与选项模式
ASP.NET Core 的配置系统并不是“读取一个 JSON 文件”这么简单,它本质上是一条由多个配置源叠加而成的键值管道。理解这条管道,才能正确使用 appsettings.json、多环境覆盖、选项模式以及密钥管理。
一、配置管道:多源叠加与后加载优先
应用启动时,WebApplicationBuilder 会按固定顺序注册默认配置源,后加入的源会覆盖先加入的同名键。典型顺序是:appsettings.json → appsettings.{Environment}.json → 用户机密(仅开发环境)→ 环境变量 → 命令行参数。因此生产环境用环境变量覆盖 JSON 中的默认值,是标准做法。
配置键使用冒号分隔层级,例如 ConnectionStrings:Default 对应 JSON 中的嵌套对象。环境变量中冒号需替换为双下划线,写作 ConnectionStrings__Default。
二、IConfiguration 的直接读取
在最小 API 或控制器中,可以直接注入 IConfiguration 读取值:
var builder = WebApplication.CreateBuilder(args);
// 读取单个值
string? conn = builder.Configuration.GetConnectionString("Default");
int timeout = builder.Configuration.GetValue<int>("Http:TimeoutSeconds", 30);
// 绑定到强类型对象
var jwtSection = builder.Configuration.GetSection("Jwt");
builder.Services.Configure<JwtOptions>(jwtSection);
直接读取适合少量、零散的值;一旦配置项成组出现,就应使用选项模式,避免字符串键散落在代码各处。
三、选项模式:三种生命周期的选择
选项模式把配置节绑定到 POCO 类,并通过 DI 注入。三种接口的差异决定了使用场景:
| 接口 | 生命周期 | 是否感知变更 | 典型场景 |
|---|---|---|---|
| IOptions<T> | Singleton | 否 | 启动后不变的配置 |
| IOptionsSnapshot<T> | Scoped | 是(每请求) | 请求级读取最新值 |
| IOptionsMonitor<T> | Singleton | 是(实时回调) | 后台服务、热更新 |
注意:IOptionsSnapshot 是 Scoped,不能注入到 Singleton 服务中,否则会抛出生命周期不匹配异常。后台服务应使用 IOptionsMonitor,并通过 OnChange 注册回调:
public class CacheWarmupService : BackgroundService
{
private readonly IOptionsMonitor<CacheOptions> _options;
public CacheWarmupService(IOptionsMonitor<CacheOptions> options)
{
_options = options;
_options.OnChange(opt =>
Console.WriteLine($"缓存容量变更为 {opt.Capacity}"));
}
protected override async Task ExecuteAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var capacity = _options.CurrentValue.Capacity;
// 使用最新配置执行预热逻辑
await Task.Delay(TimeSpan.FromMinutes(5), token);
}
}
}
四、多环境配置的落地方式
环境由 ASPNETCORE_ENVIRONMENT 决定,常见值为 Development、Staging、Production。启动时按环境名加载对应文件,例如 appsettings.Production.json。生产环境不应把连接字符串写进 JSON,而应通过环境变量或密钥服务注入。
launchSettings.json 中为不同 profile 设置 ASPNETCORE_ENVIRONMENT,本地调试即可自动切换配置。五、自定义配置源
当配置来自数据库、配置中心或自定义文件格式时,可实现 IConfigurationSource 与 ConfigurationProvider:
public class DbConfigSource : IConfigurationSource
{
public string ConnectionString { get; set; } = "";
public IConfigurationProvider Build(IConfigurationBuilder builder)
=> new DbConfigProvider(ConnectionString);
}
public class DbConfigProvider : ConfigurationProvider
{
private readonly string _conn;
public DbConfigProvider(string conn) => _conn = conn;
public override void Load()
{
// 从数据库读取键值对,填充 Data 字典
Data["App:FeatureFlag"] = "true";
}
}
// 注册
builder.Configuration.Add(new DbConfigSource
{
ConnectionString = builder.Configuration.GetConnectionString("ConfigDb")!
});
若需要热更新,可让 Provider 实现 IDisposable 并调用 OnReload() 触发变更通知。
六、用户机密与 Azure Key Vault
开发阶段用用户机密避免把敏感值提交到仓库。在项目目录执行:
dotnet user-secrets init
dotnet user-secrets set "Jwt:SigningKey" "dev-only-32-char-secret-key-0001"
dotnet user-secrets list
机密存储在用户配置目录,不进入项目文件。生产环境则推荐 Azure Key Vault,通过 DefaultAzureCredential 免密访问:
builder.Configuration.AddAzureKeyVault(
new Uri("https://my-vault.vault.azure.net/"),
new DefaultAzureCredential());
Key Vault 中的密钥名不能含冒号,需用双横线替代,例如 Jwt--SigningKey,加载后会自动映射为 Jwt:SigningKey。这样代码无需改动,仅靠配置源顺序即可完成从开发到生产的切换。
综合来看,配置系统的核心是“源顺序决定覆盖、选项模式决定读取方式、密钥服务决定安全边界”。把这三件事分开处理,多环境部署和敏感信息管理就会变得清晰可控。