configuration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Configuration

配置

Core Principles

核心原则

  1. Options pattern always — Never read
    IConfiguration
    directly in services. Bind configuration sections to strongly-typed classes with validation.
  2. Validate on startup — Use
    ValidateDataAnnotations()
    and
    ValidateOnStart()
    to catch misconfiguration before the first request.
  3. Secrets never in source — Use user secrets in development, Azure Key Vault or environment variables in production. Never commit secrets to git.
  4. Configuration layering
    appsettings.json
    appsettings.{Environment}.json
    → environment variables → user secrets. Later sources override earlier ones.
  1. 始终使用Options模式 — 绝不在服务中直接读取
    IConfiguration
    。将配置段绑定到带验证的强类型类。
  2. 启动时验证 — 使用
    ValidateDataAnnotations()
    ValidateOnStart()
    在首次请求前捕获配置错误。
  3. 密钥永不存入源码 — 开发环境使用用户密钥,生产环境使用Azure Key Vault或环境变量。绝不将密钥提交到git。
  4. 配置分层
    appsettings.json
    appsettings.{Environment}.json
    → 环境变量 → 用户密钥。后续来源会覆盖之前的来源。

Patterns

模式

Options Pattern

Options模式

csharp
// Options class with validation attributes
public class DatabaseOptions
{
    public const string SectionName = "Database";

    [Required]
    public required string ConnectionString { get; init; }

    [Range(1, 100)]
    public int MaxRetryCount { get; init; } = 3;

    [Range(1, 60)]
    public int CommandTimeoutSeconds { get; init; } = 30;
}

// Registration with validation
builder.Services.AddOptions<DatabaseOptions>()
    .BindConfiguration(DatabaseOptions.SectionName)
    .ValidateDataAnnotations()
    .ValidateOnStart(); // Fails at startup if configuration is invalid
json
// appsettings.json
{
  "Database": {
    "ConnectionString": "",
    "MaxRetryCount": 3,
    "CommandTimeoutSeconds": 30
  }
}
csharp
// 带验证属性的Options类
public class DatabaseOptions
{
    public const string SectionName = "Database";

    [Required]
    public required string ConnectionString { get; init; }

    [Range(1, 100)]
    public int MaxRetryCount { get; init; } = 3;

    [Range(1, 60)]
    public int CommandTimeoutSeconds { get; init; } = 30;
}

// 带验证的注册
builder.Services.AddOptions<DatabaseOptions>()
    .BindConfiguration(DatabaseOptions.SectionName)
    .ValidateDataAnnotations()
    .ValidateOnStart(); // 若配置无效,启动时直接失败
json
// appsettings.json
{
  "Database": {
    "ConnectionString": "",
    "MaxRetryCount": 3,
    "CommandTimeoutSeconds": 30
  }
}

Injecting Options

注入Options

csharp
// IOptions<T> — singleton, read once at startup, doesn't change
public class OrderService(IOptions<DatabaseOptions> options)
{
    private readonly DatabaseOptions _db = options.Value;
}

// IOptionsSnapshot<T> — scoped, re-reads per request (for reloadable config)
public class OrderService(IOptionsSnapshot<DatabaseOptions> options)
{
    private readonly DatabaseOptions _db = options.Value;
}

// IOptionsMonitor<T> — singleton, actively watches for changes
public class BackgroundWorker(IOptionsMonitor<WorkerOptions> options)
{
    public void DoWork()
    {
        var current = options.CurrentValue; // Always latest
    }
}
csharp
// IOptions<T> — 单例,启动时读取一次,不会变更
public class OrderService(IOptions<DatabaseOptions> options)
{
    private readonly DatabaseOptions _db = options.Value;
}

// IOptionsSnapshot<T> — 作用域,每个请求重新读取(适用于可重载配置)
public class OrderService(IOptionsSnapshot<DatabaseOptions> options)
{
    private readonly DatabaseOptions _db = options.Value;
}

// IOptionsMonitor<T> — 单例,主动监听变更
public class BackgroundWorker(IOptionsMonitor<WorkerOptions> options)
{
    public void DoWork()
    {
        var current = options.CurrentValue; // 始终获取最新值
    }
}

Custom Validation (Complex Rules)

自定义验证(复杂规则)

csharp
builder.Services.AddOptions<JwtOptions>()
    .BindConfiguration("Jwt")
    .Validate(options =>
    {
        if (string.IsNullOrEmpty(options.Key) || options.Key.Length < 32)
            return false;
        if (options.ExpirationMinutes <= 0)
            return false;
        return true;
    }, "JWT key must be at least 32 characters and expiration must be positive")
    .ValidateOnStart();
csharp
builder.Services.AddOptions<JwtOptions>()
    .BindConfiguration("Jwt")
    .Validate(options =>
    {
        if (string.IsNullOrEmpty(options.Key) || options.Key.Length < 32)
            return false;
        if (options.ExpirationMinutes <= 0)
            return false;
        return true;
    }, "JWT密钥长度至少为32字符,且过期时间必须为正数")
    .ValidateOnStart();

Azure Key Vault (Production)

Azure Key Vault(生产环境)

csharp
// Program.cs — add Key Vault as a configuration source
if (builder.Environment.IsProduction())
{
    var keyVaultUri = new Uri(builder.Configuration["KeyVault:Uri"]!);
    builder.Configuration.AddAzureKeyVault(keyVaultUri, new DefaultAzureCredential());
}
csharp
// Program.cs — 添加Key Vault作为配置源
if (builder.Environment.IsProduction())
{
    var keyVaultUri = new Uri(builder.Configuration["KeyVault:Uri"]!);
    builder.Configuration.AddAzureKeyVault(keyVaultUri, new DefaultAzureCredential());
}

Configuration for Multiple Environments

多环境配置

csharp
// Named options — different config per named instance
builder.Services.AddOptions<SmtpOptions>("internal")
    .BindConfiguration("Smtp:Internal");
builder.Services.AddOptions<SmtpOptions>("customer")
    .BindConfiguration("Smtp:Customer");

// Usage
public class EmailService(IOptionsSnapshot<SmtpOptions> options)
{
    public async Task SendInternalEmail(string to, string body)
    {
        var smtp = options.Get("internal");
        // ...
    }
}
csharp
// 命名Options — 不同命名实例对应不同配置
builder.Services.AddOptions<SmtpOptions>("internal")
    .BindConfiguration("Smtp:Internal");
builder.Services.AddOptions<SmtpOptions>("customer")
    .BindConfiguration("Smtp:Customer");

// 使用方式
public class EmailService(IOptionsSnapshot<SmtpOptions> options)
{
    public async Task SendInternalEmail(string to, string body)
    {
        var smtp = options.Get("internal");
        // ...
    }
}

Anti-patterns

反模式

Don't Read IConfiguration Directly

不要直接读取IConfiguration

csharp
// BAD — stringly-typed, no validation, hard to test
public class OrderService(IConfiguration config)
{
    public void Process()
    {
        var timeout = int.Parse(config["Database:CommandTimeout"]!);
    }
}

// GOOD — strongly-typed options
public class OrderService(IOptions<DatabaseOptions> options)
{
    public void Process()
    {
        var timeout = options.Value.CommandTimeoutSeconds;
    }
}
csharp
// 错误示例 — 字符串类型、无验证、难以测试
public class OrderService(IConfiguration config)
{
    public void Process()
    {
        var timeout = int.Parse(config["Database:CommandTimeout"]!);
    }
}

// 正确示例 — 强类型Options
public class OrderService(IOptions<DatabaseOptions> options)
{
    public void Process()
    {
        var timeout = options.Value.CommandTimeoutSeconds;
    }
}

Don't Put Secrets in appsettings.json

不要将密钥存入appsettings.json

json
// BAD — committed to source control
{
  "Jwt": { "Key": "super-secret-key" },
  "Database": { "ConnectionString": "Server=prod;Password=secret" }
}

// GOOD — appsettings.json has defaults/structure only
{
  "Jwt": { "Key": "", "Issuer": "myapp", "Audience": "myapp" },
  "Database": { "ConnectionString": "" }
}
// Secrets provided via user-secrets (dev) or env vars / Key Vault (prod)
json
// 错误示例 — 会提交到版本控制
{
  "Jwt": { "Key": "super-secret-key" },
  "Database": { "ConnectionString": "Server=prod;Password=secret" }
}

// 正确示例 — appsettings.json仅包含默认值/结构
{
  "Jwt": { "Key": "", "Issuer": "myapp", "Audience": "myapp" },
  "Database": { "ConnectionString": "" }
}
// 密钥通过用户密钥(开发环境)或环境变量/Key Vault(生产环境)提供

Don't Skip Startup Validation

不要跳过启动验证

csharp
// BAD — misconfiguration discovered at runtime
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));

// GOOD — fail fast at startup
builder.Services.AddOptions<JwtOptions>()
    .BindConfiguration("Jwt")
    .ValidateDataAnnotations()
    .ValidateOnStart();
csharp
// 错误示例 — 配置错误在运行时才被发现
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));

// 正确示例 — 启动时快速失败
builder.Services.AddOptions<JwtOptions>()
    .BindConfiguration("Jwt")
    .ValidateDataAnnotations()
    .ValidateOnStart();

Decision Guide

决策指南

ScenarioRecommendation
Binding config to classOptions pattern with
BindConfiguration
Simple, immutable config
IOptions<T>
Config that changes per request
IOptionsSnapshot<T>
Background service watching config
IOptionsMonitor<T>
Development secrets
dotnet user-secrets
Production secretsAzure Key Vault or environment variables
Validating config
ValidateDataAnnotations()
+
ValidateOnStart()
Multiple configs of same typeNamed options with
IOptionsSnapshot<T>.Get(name)
场景推荐方案
将配置绑定到类使用Options模式配合
BindConfiguration
简单、不可变配置
IOptions<T>
每个请求会变更的配置
IOptionsSnapshot<T>
监听配置变更的后台服务
IOptionsMonitor<T>
开发环境密钥
dotnet user-secrets
生产环境密钥Azure Key Vault或环境变量
配置验证
ValidateDataAnnotations()
+
ValidateOnStart()
同一类型的多套配置命名Options配合
IOptionsSnapshot<T>.Get(name)