Skip to main content

.NET 配置管理

配置系统概述

核心概念

ASP.NET Core 使用基于键值对的配置系统,支持多种配置源:

  • JSON 文件(appsettings.json)
  • 环境变量
  • 命令行参数
  • Azure Key Vault
  • 用户机密(User Secrets)
  • 内存配置

配置文件

1. appsettings.json

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyDb;User=sa;Password=Pass@123"
},
"JwtSettings": {
"Secret": "your-secret-key-here-should-be-very-long",
"Issuer": "your-app",
"Audience": "your-app-users",
"ExpirationMinutes": 60
},
"EmailSettings": {
"SmtpServer": "smtp.gmail.com",
"SmtpPort": 587,
"SenderEmail": "noreply@example.com",
"SenderName": "My App"
},
"CacheSettings": {
"DefaultExpirationMinutes": 30,
"EnableDistributedCache": true,
"RedisConnection": "localhost:6379"
},
"AllowedHosts": "*"
}

2. 配置优先级

配置覆盖顺序

配置值按以下顺序加载,后面的会覆盖前面的:

  1. appsettings.json
  2. appsettings.{Environment}.json
  3. User Secrets(仅开发环境)
  4. 环境变量
  5. 命令行参数

读取配置

1. 基本读取方式

public class MyService
{
private readonly IConfiguration _configuration;

public MyService(IConfiguration configuration)
{
_configuration = configuration;
}

public void UseConfiguration()
{
// 读取简单值
var allowedHosts = _configuration["AllowedHosts"];

// 读取嵌套值
var logLevel = _configuration["Logging:LogLevel:Default"];

// 读取连接字符串
var connString = _configuration.GetConnectionString("DefaultConnection");

// 读取并转换类型
var port = _configuration.GetValue<int>("EmailSettings:SmtpPort");

// 提供默认值
var timeout = _configuration.GetValue<int>("Timeout", 30);
}
}

Options 模式

1. 基本用法

推荐使用 Options 模式

Options 模式提供强类型配置、依赖注入和验证支持

// 1. 定义配置类
public class EmailSettings
{
public const string SectionName = "EmailSettings";

public string SmtpServer { get; set; } = string.Empty;
public int SmtpPort { get; set; }
public string SenderEmail { get; set; } = string.Empty;
public string SenderName { get; set; } = string.Empty;
public string? Username { get; set; }
public string? Password { get; set; }
public bool EnableSsl { get; set; } = true;
}

// 2. 注册配置
var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection(EmailSettings.SectionName));

// 3. 使用配置
public class EmailService
{
private readonly EmailSettings _settings;

public EmailService(IOptions<EmailSettings> options)
{
_settings = options.Value;
}

public async Task SendEmailAsync(string to, string subject, string body)
{
using var client = new SmtpClient(_settings.SmtpServer, _settings.SmtpPort)
{
Credentials = new NetworkCredential(
_settings.Username,
_settings.Password),
EnableSsl = _settings.EnableSsl
};

var message = new MailMessage
{
From = new MailAddress(_settings.SenderEmail, _settings.SenderName),
Subject = subject,
Body = body,
IsBodyHtml = true
};
message.To.Add(to);

await client.SendMailAsync(message);
}
}

2. IOptions vs IOptionsSnapshot vs IOptionsMonitor

接口生命周期配置重载命名选项使用场景
IOptions<T>单例❌ 不支持✅ 支持配置不变
IOptionsSnapshot<T>作用域✅ 支持✅ 支持请求内配置可变
IOptionsMonitor<T>单例✅ 支持✅ 支持实时监控配置变化
// 适用于配置在应用生命周期内不变的场景
public class MyService
{
private readonly JwtSettings _settings;

public MyService(IOptions<JwtSettings> options)
{
// 配置在构造时读取一次,不会再变
_settings = options.Value;
}
}

3. 命名选项

// 定义多个不同的配置实例
public class DatabaseSettings
{
public string ConnectionString { get; set; } = string.Empty;
public int MaxRetryCount { get; set; }
public int CommandTimeout { get; set; }
}

// 注册多个命名选项
builder.Services.Configure<DatabaseSettings>("Primary",
builder.Configuration.GetSection("Database:Primary"));

builder.Services.Configure<DatabaseSettings>("Secondary",
builder.Configuration.GetSection("Database:Secondary"));

// 使用命名选项
public class DataService
{
private readonly DatabaseSettings _primaryDb;
private readonly DatabaseSettings _secondaryDb;

public DataService(IOptionsSnapshot<DatabaseSettings> options)
{
_primaryDb = options.Get("Primary");
_secondaryDb = options.Get("Secondary");
}
}

配置验证

1. 数据注解验证

using System.ComponentModel.DataAnnotations;

public class JwtSettings
{
public const string SectionName = "JwtSettings";

[Required(ErrorMessage = "JWT Secret 是必需的")]
[MinLength(32, ErrorMessage = "Secret 长度至少 32 个字符")]
public string Secret { get; set; } = string.Empty;

[Required]
public string Issuer { get; set; } = string.Empty;

[Required]
public string Audience { get; set; } = string.Empty;

[Range(1, 1440, ErrorMessage = "过期时间必须在 1-1440 分钟之间")]
public int ExpirationMinutes { get; set; }
}

// 注册时启用验证
builder.Services
.AddOptions<JwtSettings>()
.BindConfiguration(JwtSettings.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart(); // 应用启动时验证

2. 自定义验证

public class EmailSettings
{
public string SmtpServer { get; set; } = string.Empty;
public int SmtpPort { get; set; }
public string SenderEmail { get; set; } = string.Empty;
}

// 自定义验证逻辑
builder.Services
.AddOptions<EmailSettings>()
.BindConfiguration(EmailSettings.SectionName)
.Validate(settings =>
{
if (string.IsNullOrEmpty(settings.SmtpServer))
return false;

if (settings.SmtpPort <= 0 || settings.SmtpPort > 65535)
return false;

if (!settings.SenderEmail.Contains("@"))
return false;

return true;
}, "Email 配置无效")
.ValidateOnStart();

3. IValidateOptions 接口

public class JwtSettingsValidator : IValidateOptions<JwtSettings>
{
public ValidateOptionsResult Validate(string? name, JwtSettings options)
{
var errors = new List<string>();

if (string.IsNullOrEmpty(options.Secret))
{
errors.Add("Secret 不能为空");
}
else if (options.Secret.Length < 32)
{
errors.Add("Secret 长度必须至少 32 个字符");
}

if (string.IsNullOrEmpty(options.Issuer))
{
errors.Add("Issuer 不能为空");
}

if (options.ExpirationMinutes <= 0)
{
errors.Add("ExpirationMinutes 必须大于 0");
}

if (errors.Any())
{
return ValidateOptionsResult.Fail(errors);
}

return ValidateOptionsResult.Success;
}
}

// 注册验证器
builder.Services.AddSingleton<IValidateOptions<JwtSettings>, JwtSettingsValidator>();

builder.Services
.AddOptions<JwtSettings>()
.BindConfiguration(JwtSettings.SectionName)
.ValidateOnStart();

环境变量

1. 读取环境变量

// 直接读取
var dbPassword = Environment.GetEnvironmentVariable("DB_PASSWORD");

// 通过配置系统读取(自动映射)
var dbPassword = builder.Configuration["DB_PASSWORD"];

// 嵌套配置使用 __ 分隔符
// 环境变量: ConnectionStrings__DefaultConnection
// 映射到: ConnectionStrings:DefaultConnection
var connString = builder.Configuration["ConnectionStrings:DefaultConnection"];

2. Docker 容器配置

# docker-compose.yml
version: '3.8'
services:
api:
image: myapi:latest
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__DefaultConnection=Server=db;Database=MyDb;User=sa;Password=Pass@123
- JwtSettings__Secret=${JWT_SECRET}
- JwtSettings__ExpirationMinutes=60
- EmailSettings__SmtpServer=smtp.gmail.com
- EmailSettings__SmtpPort=587

3. Azure App Service 配置

// Azure App Service 应用程序设置会自动映射为环境变量
// 应用程序设置名称: ConnectionStrings__DefaultConnection
// 在代码中读取:
var connString = builder.Configuration.GetConnectionString("DefaultConnection");

User Secrets (开发环境)

1. 初始化 User Secrets

# 在项目目录下执行
dotnet user-secrets init

# 添加密钥
dotnet user-secrets set "JwtSettings:Secret" "my-super-secret-key-for-development-only"
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=MyDb;..."

# 列出所有密钥
dotnet user-secrets list

# 删除密钥
dotnet user-secrets remove "JwtSettings:Secret"

# 清除所有密钥
dotnet user-secrets clear

2. 在代码中使用

// User Secrets 仅在开发环境自动加载
var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
// User Secrets 已自动添加到配置中
var secret = builder.Configuration["JwtSettings:Secret"];
}

Azure Key Vault

1. 安装 NuGet 包

dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
dotnet add package Azure.Identity

2. 配置 Key Vault

var builder = WebApplication.CreateBuilder(args);

// 方式 1:使用 DefaultAzureCredential(推荐)
if (!builder.Environment.IsDevelopment())
{
var keyVaultEndpoint = new Uri(builder.Configuration["KeyVaultEndpoint"]!);
builder.Configuration.AddAzureKeyVault(
keyVaultEndpoint,
new DefaultAzureCredential());
}

// 方式 2:使用客户端密钥
var keyVaultEndpoint = builder.Configuration["KeyVault:Endpoint"];
var tenantId = builder.Configuration["KeyVault:TenantId"];
var clientId = builder.Configuration["KeyVault:ClientId"];
var clientSecret = builder.Configuration["KeyVault:ClientSecret"];

builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultEndpoint!),
new ClientSecretCredential(tenantId, clientId, clientSecret));

// Key Vault 中的密钥会自动映射
// Key Vault 密钥: ConnectionStrings--DefaultConnection
// 映射到: ConnectionStrings:DefaultConnection

配置最佳实践

最佳实践清单

1. 使用强类型配置

// ✅ 好:使用 Options 模式
public class MyService
{
private readonly JwtSettings _settings;

public MyService(IOptions<JwtSettings> options)
{
_settings = options.Value;
}
}

// ❌ 差:直接注入 IConfiguration
public class MyService
{
private readonly IConfiguration _configuration;

public MyService(IConfiguration configuration)
{
_configuration = configuration;
}

public void DoWork()
{
var secret = _configuration["JwtSettings:Secret"]; // 字符串,易出错
}
}

2. 配置验证

// ✅ 好:启动时验证配置
builder.Services
.AddOptions<JwtSettings>()
.BindConfiguration("JwtSettings")
.ValidateDataAnnotations()
.ValidateOnStart();

// ❌ 差:运行时才发现配置错误

3. 敏感信息不提交到代码库

// ✅ 好:使用 User Secrets 或环境变量
// 开发环境:dotnet user-secrets set "ApiKey" "secret-key"
// 生产环境:环境变量或 Key Vault

// ❌ 差:硬编码敏感信息
public const string ApiKey = "my-secret-key"; // 不要这样做!

// ❌ 差:提交 appsettings.json 中的密码
{
"ConnectionStrings": {
"DefaultConnection": "Server=...;Password=RealPassword123" // 不要提交!
}
}

4. 环境特定配置

// appsettings.json - 基础配置
{
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}

// appsettings.Development.json - 开发环境覆盖
{
"Logging": {
"LogLevel": {
"Default": "Debug"
}
}
}

// appsettings.Production.json - 生产环境覆盖
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}

5. 使用常量避免魔法字符串

// ✅ 好
public class JwtSettings
{
public const string SectionName = "JwtSettings";
public string Secret { get; set; } = string.Empty;
}

builder.Services.Configure<JwtSettings>(
builder.Configuration.GetSection(JwtSettings.SectionName));

// ❌ 差
builder.Services.Configure<JwtSettings>(
builder.Configuration.GetSection("JwtSettings")); // 魔法字符串

完整示例

配置类定义

public class AppSettings
{
public const string SectionName = "AppSettings";

public string AppName { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
public FeatureFlags Features { get; set; } = new();
public ApiSettings Api { get; set; } = new();
}

public class FeatureFlags
{
public bool EnableCache { get; set; }
public bool EnableSwagger { get; set; }
public bool EnableRateLimiting { get; set; }
}

public class ApiSettings
{
public int MaxPageSize { get; set; }
public int DefaultPageSize { get; set; }
public int RequestTimeoutSeconds { get; set; }
}

Program.cs 配置

var builder = WebApplication.CreateBuilder(args);

// 添加配置源
builder.Configuration
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables()
.AddCommandLine(args);

// 开发环境添加 User Secrets
if (builder.Environment.IsDevelopment())
{
builder.Configuration.AddUserSecrets<Program>();
}

// 生产环境添加 Azure Key Vault
if (builder.Environment.IsProduction())
{
var keyVaultEndpoint = builder.Configuration["KeyVaultEndpoint"];
if (!string.IsNullOrEmpty(keyVaultEndpoint))
{
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultEndpoint),
new DefaultAzureCredential());
}
}

// 注册配置
builder.Services
.AddOptions<AppSettings>()
.BindConfiguration(AppSettings.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart();

builder.Services
.AddOptions<JwtSettings>()
.BindConfiguration(JwtSettings.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart();

var app = builder.Build();

// 验证关键配置在启动时可用
var appSettings = app.Services.GetRequiredService<IOptions<AppSettings>>().Value;
Console.WriteLine($"Starting {appSettings.AppName} v{appSettings.Version}");

app.Run();

总结

关键要点
  • 使用 Options 模式实现强类型配置
  • 利用配置优先级处理不同环境
  • 使用 User Secrets 保护开发环境敏感信息
  • 生产环境使用环境变量或 Azure Key Vault
  • 启用配置验证,启动时发现问题
  • 选择合适的 IOptions 接口(IOptions/IOptionsSnapshot/IOptionsMonitor)
  • 不要在代码库中提交敏感信息

相关资源