.NET 配置管理
配置系统概述
核心概念
ASP.NET Core 使用基于键值对的配置系统,支持多种配置源:
- JSON 文件(appsettings.json)
- 环境变量
- 命令行参数
- Azure Key Vault
- 用户机密(User Secrets)
- 内存配置
配置文件
1. appsettings.json
- appsettings.json
- appsettings.Development.json
- appsettings.Production.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": "*"
}
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Information"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyDb_Dev;User=sa;Password=DevPass@123"
},
"JwtSettings": {
"ExpirationMinutes": 120
},
"EmailSettings": {
"SmtpServer": "localhost",
"SmtpPort": 25
}
}
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "从环境变量或 Azure Key Vault 获取"
},
"CacheSettings": {
"RedisConnection": "从环境变量获取"
}
}
2. 配置优先级
配置覆盖顺序
配置值按以下顺序加载,后面的会覆盖前面的:
- appsettings.json
- appsettings.{Environment}.json
- User Secrets(仅开发环境)
- 环境变量
- 命令行参数
读取配置
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);
}
}
public void UseConfigurationSection()
{
// 获取配置节
var jwtSection = _configuration.GetSection("JwtSettings");
var secret = jwtSection["Secret"];
var issuer = jwtSection["Issuer"];
var expiration = jwtSection.GetValue<int>("ExpirationMinutes");
// 检查配置节是否存在
if (jwtSection.Exists())
{
// 配置节存在
}
}
public class JwtSettings
{
public string Secret { get; set; } = string.Empty;
public string Issuer { get; set; } = string.Empty;
public string Audience { get; set; } = string.Empty;
public int ExpirationMinutes { get; set; }
}
public void BindToObject()
{
// 方式 1:手动绑定
var jwtSettings = new JwtSettings();
_configuration.GetSection("JwtSettings").Bind(jwtSettings);
// 方式 2:Get<T>
var jwtSettings2 = _configuration
.GetSection("JwtSettings")
.Get<JwtSettings>();
}
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> | 单例 | ✅ 支持 | ✅ 支持 | 实时监控配置变化 |
- IOptions
- IOptionsSnapshot
- IOptionsMonitor
// 适用于配置在应用生命周期内不变的场景
public class MyService
{
private readonly JwtSettings _settings;
public MyService(IOptions<JwtSettings> options)
{
// 配置在构造时读取一次,不会再变
_settings = options.Value;
}
}
// 适用于配置可能在不同请求间变化的场景
public class MyController : ControllerBase
{
private readonly CacheSettings _settings;
public MyController(IOptionsSnapshot<CacheSettings> options)
{
// 每个请求读取最新配置
_settings = options.Value;
}
[HttpGet]
public IActionResult Get()
{
// 使用当前请求的配置
var expiration = _settings.DefaultExpirationMinutes;
return Ok(expiration);
}
}
// 适用于需要实时监控配置变化的场景
public class CacheService
{
private readonly IOptionsMonitor<CacheSettings> _optionsMonitor;
private CacheSettings _currentSettings;
public CacheService(IOptionsMonitor<CacheSettings> optionsMonitor)
{
_optionsMonitor = optionsMonitor;
_currentSettings = _optionsMonitor.CurrentValue;
// 监听配置变化
_optionsMonitor.OnChange(settings =>
{
Console.WriteLine("配置已更新!");
_currentSettings = settings;
ReloadCache();
});
}
private void ReloadCache()
{
// 重新加载缓存
}
public int GetCacheExpiration()
{
// 总是获取最新配置
return _optionsMonitor.CurrentValue.DefaultExpirationMinutes;
}
}
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)
- 不要在代码库中提交敏感信息