【C# .NET Core 实战系列】C# 配置管理实战:从 appsettings.json 到 Azure Key Vault,多环境配置完全指南
📚 系列导航
本文是 C# .NET Core 实战系列 的第 14 篇文章
| 01 | 依赖注入深度解析 | DI 容器、服务生命周期、最佳实践 |
| 02 | 中间件管道详解 | 请求管道、自定义中间件、执行顺序 |
| 03 | 日志系统实战 | ILogger、Serilog、结构化日志 |
| 04 | 数据库访问层设计 | Repository 模式、EF Core、Dapper |
| 05 | API 接口设计规范 | RESTful、版本控制、响应封装 |
| 06 | JWT 认证授权 | Token 生成、验证、刷新机制 |
| 07 | 缓存策略优化 | MemoryCache、Redis、分布式缓存 |
| 08 | 后台任务处理 | BackgroundService、Hangfire |
| 09 | 文件上传下载 | IFormFile、流式处理、大文件分片 |
| 10 | 性能优化实战 | 异步编程、连接池、响应压缩 |
| 11 | 单元测试进阶 | xUnit、Moq、测试驱动开发 |
| 12 | Docker 容器化部署 | Dockerfile、多阶段构建、Compose |
| 13 | CI/CD 流水线搭建 | GitHub Actions、自动化部署 |
| 14 | 配置管理实战 | 多环境配置、Options 模式、敏感数据保护 |
| 15 | 全局异常处理实战 | 异常过滤器、统一错误响应 |
前言:为什么配置管理很重要?
在实际项目开发中,配置管理是一个容易被忽视但又极其重要的环节。你是否遇到过以下问题:
- 🔴 配置硬编码:数据库连接字符串直接写在代码里,每次发布都要重新编译
- 🔴 环境混乱:开发环境和生产环境配置混在一起,一不小心就删了生产数据
- 🔴 敏感信息泄露:API 密钥、数据库密码直接提交到 Git 仓库
- 🔴 配置修改麻烦:改个配置项需要重启整个应用
.NET Core 提供了一套强大而灵活的配置系统,可以完美解决上述所有问题。本文将从基础到进阶,带你全面掌握 C# 配置管理的最佳实践。
第一部分:IConfiguration 基础使用
1.1 配置系统的核心概念
.NET Core 的配置系统基于 IConfiguration 接口,支持多种配置源:
┌─────────────────────────────────────────────────────────────┐
│ Configuration Providers │
├─────────────┬─────────────┬─────────────┬─────────────────┤
│ JSON Files │ Env Vars │ Command Line│ Azure Key Vault │
│ appsettings │ ASPNETCORE_ │ –key=value │ https://… │
├─────────────┼─────────────┼─────────────┼─────────────────┤
│ INI Files │ User Secrets│ XML Files │ Custom Providers│
│ config.ini │ %APPDATA% │ config.xml │ Your Source │
└─────────────┴─────────────┴─────────────┴─────────────────┘
│
▼
┌─────────────────┐
│ IConfiguration │
│ (Key-Value) │
└─────────────────┘
1.2 默认配置加载顺序
在 ASP.NET Core Web 项目中,默认的配置加载顺序如下(后加载的会覆盖先加载的):
// Program.cs – .NET 6+ 默认模板
var builder = WebApplication.CreateBuilder(args);
// 默认已按以下顺序加载:
// 1. appsettings.json
// 2. appsettings.{Environment}.json
// 3. 环境变量
// 4. 命令行参数
var app = builder.Build();
1.3 基本读取方式
首先,创建一个基础的 appsettings.json 文件:
{
"Application": {
"Name": "MyAwesomeApp",
"Version": "1.0.0",
"Environment": "Development"
},
"Database": {
"ConnectionString": "Server=localhost;Database=MyApp;Trusted_Connection=True;",
"CommandTimeout": 30
},
"Features": {
"EnableCache": true,
"MaxRetryCount": 3
}
}
方式一:直接注入 IConfiguration
using Microsoft.Extensions.Configuration;
public class MyService
{
private readonly IConfiguration _configuration;
public MyService(IConfiguration configuration)
{
_configuration = configuration;
}
public void Demo()
{
// 使用冒号分隔符访问嵌套配置
string appName = _configuration["Application:Name"];
string version = _configuration["Application:Version"];
string connStr = _configuration["Database:ConnectionString"];
// 获取整数值(带默认值)
int timeout = _configuration.GetValue<int>("Database:CommandTimeout", 30);
// 获取布尔值
bool enableCache = _configuration.GetValue<bool>("Features:EnableCache");
Console.WriteLine($"应用名称: {appName}, 版本: {version}");
Console.WriteLine($"缓存启用: {enableCache}, 超时: {timeout}秒");
}
}
方式二:使用 GetSection 获取配置节
public void DemoWithSection()
{
// 获取整个配置节
IConfigurationSection dbSection = _configuration.GetSection("Database");
string connStr = dbSection["ConnectionString"];
int timeout = dbSection.GetValue<int>("CommandTimeout");
// 检查配置节是否存在
if (dbSection.Exists())
{
Console.WriteLine($"数据库连接: {connStr}");
}
}
方式三:绑定到 POCO 对象
// 定义配置类
public class ApplicationSettings
{
public string Name { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
public string Environment { get; set; } = string.Empty;
}
public class DatabaseSettings
{
public string ConnectionString { get; set; } = string.Empty;
public int CommandTimeout { get; set; } = 30;
}
// 使用方式
public void DemoWithBinding()
{
var appSettings = new ApplicationSettings();
_configuration.GetSection("Application").Bind(appSettings);
// 或者使用 Get<T> 方法(推荐)
var dbSettings = _configuration.GetSection("Database").Get<DatabaseSettings>();
Console.WriteLine($"应用: {appSettings.Name} v{appSettings.Version}");
}
1.4 在 Controller 中使用配置
[ApiController]
[Route("api/[controller]")]
public class ConfigController : ControllerBase
{
private readonly IConfiguration _configuration;
public ConfigController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpGet("app-info")]
public IActionResult GetAppInfo()
{
return Ok(new
{
Name = _configuration["Application:Name"],
Version = _configuration["Application:Version"],
Environment = _configuration["Application:Environment"]
});
}
}
第二部分:多环境配置(Development、Staging、Production)
2.1 环境配置文件结构
多环境配置是实际项目中的标准做法,通过不同环境对应的配置文件实现配置隔离:
项目根目录/
├── appsettings.json # 基础配置(所有环境共享)
├── appsettings.Development.json # 开发环境配置
├── appsettings.Staging.json # 预发布环境配置
└── appsettings.Production.json # 生产环境配置
2.2 各环境配置文件示例
appsettings.json(基础配置)
{
"Application": {
"Name": "MyAwesomeApp"
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"AllowedHosts": "*"
}
appsettings.Development.json
{
"Database": {
"ConnectionString": "Server=localhost;Database=MyApp_Dev;Trusted_Connection=True;TrustServerCertificate=True;",
"CommandTimeout": 60
},
"Api": {
"BaseUrl": "https://dev-api.example.com",
"Timeout": 30000
},
"Features": {
"EnableDebugMode": true,
"EnableSwagger": true,
"EnableDetailedErrors": true
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information"
}
}
}
appsettings.Staging.json
{
"Database": {
"ConnectionString": "Server=staging-db.internal;Database=MyApp_Staging;User Id=app_user;Password=${DB_PASSWORD};",
"CommandTimeout": 45
},
"Api": {
"BaseUrl": "https://staging-api.example.com",
"Timeout": 30000
},
"Features": {
"EnableDebugMode": false,
"EnableSwagger": true,
"EnableDetailedErrors": false
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}
appsettings.Production.json
{
"Database": {
"ConnectionString": "Server=prod-db.internal;Database=MyApp_Prod;User Id=app_user;Password=${DB_PASSWORD};",
"CommandTimeout": 30
},
"Api": {
"BaseUrl": "https://api.example.com",
"Timeout": 15000
},
"Features": {
"EnableDebugMode": false,
"EnableSwagger": false,
"EnableDetailedErrors": false
},
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning"
}
}
}
2.3 配置合并规则
配置系统采用 “后加载覆盖前加载” 的策略:
加载顺序:appsettings.json → appsettings.{Environment}.json → 环境变量 → 命令行
最终配置 = 基础配置 + 环境特定配置(覆盖相同键)
示例说明:
| Application:Name | “MyAwesomeApp” | (未定义) | “MyAwesomeApp” |
| Database:CommandTimeout | (未定义) | 60 | 60 |
| Logging:LogLevel:Default | “Information” | “Debug” | “Debug” |
2.4 设置和切换环境
方式一:通过环境变量
# Windows
set ASPNETCORE_ENVIRONMENT=Production
dotnet run
# Linux/Mac
export ASPNETCORE_ENVIRONMENT=Production
dotnet run
方式二:通过命令行参数
dotnet run –environment "Staging"
方式三:在 launchSettings.json 中配置
// Properties/launchSettings.json
{
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"MyApp": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7001;http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Production": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
}
}
}
2.5 在代码中判断当前环境
public class EnvironmentAwareService
{
private readonly IWebHostEnvironment _env;
private readonly IConfiguration _configuration;
public EnvironmentAwareService(
IWebHostEnvironment env,
IConfiguration configuration)
{
_env = env;
_configuration = configuration;
}
public void DoWork()
{
// 方式一:检查环境名称
if (_env.IsDevelopment())
{
Console.WriteLine("开发环境:启用调试功能");
}
else if (_env.IsStaging())
{
Console.WriteLine("预发布环境");
}
else if (_env.IsProduction())
{
Console.WriteLine("生产环境:注意安全");
}
// 方式二:检查自定义环境
if (_env.EnvironmentName == "Testing")
{
Console.WriteLine("测试环境");
}
// 方式三:组合判断
if (_env.IsDevelopment() || _env.IsStaging())
{
// 在非生产环境执行某些操作
EnableDetailedLogging();
}
}
private void EnableDetailedLogging()
{
// 启用详细日志
}
}
2.6 根据环境条件注册服务
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 根据环境注册不同的服务实现
if (builder.Environment.IsDevelopment())
{
// 开发环境使用模拟服务
builder.Services.AddSingleton<IEmailService, MockEmailService>();
builder.Services.AddSingleton<ICacheService, LocalCacheService>();
}
else
{
// 生产环境使用真实服务
builder.Services.AddSingleton<IEmailService, SmtpEmailService>();
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
}
// 更优雅的方式:使用扩展方法
builder.Services.ConfigureEnvironmentSpecificServices(builder.Environment);
var app = builder.Build();
// ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
public static IServiceCollection ConfigureEnvironmentSpecificServices(
this IServiceCollection services,
IWebHostEnvironment env)
{
// 使用条件表达式
services.AddSingleton<IEmailService>(
env.IsDevelopment() ? typeof(MockEmailService) : typeof(SmtpEmailService));
// 或者使用 switch 表达式
services.AddSingleton<ICacheService>(_ => env.EnvironmentName switch
{
"Development" => new LocalCacheService(),
"Staging" => new RedisCacheService("staging-redis"),
"Production" => new RedisCacheService("prod-redis"),
_ => throw new InvalidOperationException($"不支持的环境: {env.EnvironmentName}")
});
return services;
}
}
第三部分:Options 模式(强类型配置)
3.1 为什么使用 Options 模式?
直接使用 IConfiguration 存在以下问题:
- ❌ 字符串键容易拼写错误
- ❌ 没有类型安全
- ❌ 配置变更无法感知
- ❌ 难以进行单元测试
Options 模式提供了解决方案:
- ✅ 强类型配置类
- ✅ 编译时类型检查
- ✅ 支持配置热更新
- ✅ 易于测试和模拟
3.2 基本使用:IOptions
定义配置类
// Models/ApiSettings.cs
public class ApiSettings
{
public const string SectionName = "Api";
public string BaseUrl { get; set; } = string.Empty;
public int Timeout { get; set; } = 30000;
public int MaxRetries { get; set; } = 3;
public bool EnableRetry { get; set; } = true;
}
// Models/CacheSettings.cs
public class CacheSettings
{
public const string SectionName = "Cache";
public bool Enabled { get; set; } = true;
public int DefaultExpirationMinutes { get; set; } = 30;
public string RedisConnectionString { get; set; } = string.Empty;
}
注册配置
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 方式一:使用 Configure 方法
builder.Services.Configure<ApiSettings>(
builder.Configuration.GetSection(ApiSettings.SectionName));
// 方式二:使用 Bind 方法(更灵活)
builder.Services.AddOptions<CacheSettings>()
.Bind(builder.Configuration.GetSection(CacheSettings.SectionName))
.ValidateDataAnnotations(); // 启用数据注解验证
var app = builder.Build();
在服务中使用
// Services/ExternalApiService.cs
public class ExternalApiService
{
private readonly ApiSettings _settings;
private readonly HttpClient _httpClient;
// 注入 IOptions<T>
public ExternalApiService(IOptions<ApiSettings> options, HttpClient httpClient)
{
_settings = options.Value; // 获取配置值
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri(_settings.BaseUrl);
_httpClient.Timeout = TimeSpan.FromMilliseconds(_settings.Timeout);
}
public async Task<string> GetDataAsync(string endpoint)
{
for (int i = 0; i < _settings.MaxRetries; i++)
{
try
{
var response = await _httpClient.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex) when (_settings.EnableRetry && i < _settings.MaxRetries – 1)
{
await Task.Delay(1000 * (i + 1)); // 指数退避
}
}
throw new HttpRequestException($"请求失败,已重试 {_settings.MaxRetries} 次");
}
}
3.3 进阶使用:IOptionsMonitor(支持热更新)
IOptionsMonitor 可以监听配置变更并自动更新:
public class CacheService : ICacheService
{
private readonly IOptionsMonitor<CacheSettings> _settingsMonitor;
private CacheSettings _currentSettings;
public CacheService(IOptionsMonitor<CacheSettings> settingsMonitor)
{
_settingsMonitor = settingsMonitor;
_currentSettings = _settingsMonitor.CurrentValue;
// 监听配置变更
_settingsMonitor.OnChange(newSettings =>
{
Console.WriteLine($"缓存配置已更新: Enabled={newSettings.Enabled}");
_currentSettings = newSettings;
OnSettingsChanged(newSettings);
});
}
private void OnSettingsChanged(CacheSettings newSettings)
{
// 处理配置变更,例如重新初始化缓存
if (newSettings.Enabled)
{
InitializeCache(newSettings);
}
}
public void Set<T>(string key, T value)
{
if (!_currentSettings.Enabled)
{
return; // 缓存未启用,直接返回
}
var expiration = TimeSpan.FromMinutes(_currentSettings.DefaultExpirationMinutes);
// 设置缓存…
}
private void InitializeCache(CacheSettings settings)
{
// 初始化缓存实现
}
}
3.4 IOptionsSnapshot(请求级别)
IOptionsSnapshot 在每次请求时重新计算配置值,适用于 Scoped 服务:
// 在 Scoped 服务中使用
public class RequestScopedService
{
private readonly CacheSettings _settings;
public RequestScopedService(IOptionsSnapshot<CacheSettings> optionsSnapshot)
{
// 每次请求都会获取最新的配置值
_settings = optionsSnapshot.Value;
}
public void DoWork()
{
Console.WriteLine($"当前缓存过期时间: {_settings.DefaultExpirationMinutes} 分钟");
}
}
3.5 配置验证
使用 Data Annotations
using System.ComponentModel.DataAnnotations;
public class DatabaseSettings
{
[Required(ErrorMessage = "连接字符串不能为空")]
public string ConnectionString { get; set; } = string.Empty;
[Range(1, 300, ErrorMessage = "命令超时必须在 1-300 秒之间")]
public int CommandTimeout { get; set; } = 30;
[Range(1, 100)]
public int MaxPoolSize { get; set; } = 100;
}
// 注册时启用验证
builder.Services.AddOptions<DatabaseSettings>()
.Bind(builder.Configuration.GetSection("Database"))
.ValidateDataAnnotations()
.ValidateOnStart(); // 应用启动时验证
使用自定义验证逻辑
builder.Services.AddOptions<ApiSettings>()
.Bind(builder.Configuration.GetSection("Api"))
.Validate(settings =>
{
// 自定义验证逻辑
if (string.IsNullOrEmpty(settings.BaseUrl))
{
return false;
}
// 验证 URL 格式
if (!Uri.TryCreate(settings.BaseUrl, UriKind.Absolute, out _))
{
return false;
}
// 生产环境必须使用 HTTPS
if (settings.BaseUrl.StartsWith("http://") &&
!settings.BaseUrl.Contains("localhost"))
{
return false;
}
return true;
}, "API 配置验证失败:BaseUrl 必须是有效的 HTTPS URL")
.ValidateOnStart();
3.6 使用验证函数(.NET 8+)
// .NET 8 引入了更简洁的验证方式
builder.Services.AddOptions<ApiSettings>()
.BindConfiguration("Api")
.Validate(settings => !string.IsNullOrEmpty(settings.BaseUrl))
.Validate(settings => settings.Timeout > 0)
.ValidateOnStart();
3.7 配置后处理
builder.Services.AddOptions<ApiSettings>()
.Bind(builder.Configuration.GetSection("Api"))
.PostConfigure(settings =>
{
// 在绑定和验证之后执行
// 可以在这里进行配置的补充处理
// 示例:确保 BaseUrl 不以斜杠结尾
if (settings.BaseUrl.EndsWith("/"))
{
settings.BaseUrl = settings.BaseUrl.TrimEnd('/');
}
// 示例:根据环境自动调整超时时间
var env = builder.Environment;
if (env.IsDevelopment())
{
settings.Timeout = Math.Max(settings.Timeout, 60000); // 开发环境至少 60 秒
}
});
第四部分:配置热更新
4.1 启用文件变更监听
默认情况下,appsettings.json 文件的变更会被监听。你可以手动控制这个行为:
var builder = WebApplication.CreateBuilder(args);
// 方式一:在 AddJsonFile 中配置
builder.Configuration
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json",
optional: true, reloadOnChange: true);
// 方式二:添加自定义配置文件并启用热更新
builder.Configuration
.AddJsonFile("custom-settings.json", optional: true, reloadOnChange: true);
4.2 实现配置热更新示例
// 定义可热更新的配置类
public class FeatureFlags
{
public bool EnableNewDashboard { get; set; }
public bool EnableBetaFeatures { get; set; }
public int MaxItemsPerPage { get; set; } = 20;
public List<string> AllowedFeatures { get; set; } = new();
}
// 服务实现
public class FeatureService : IFeatureService
{
private readonly IOptionsMonitor<FeatureFlags> _featureMonitor;
private readonly ILogger<FeatureService> _logger;
public FeatureService(
IOptionsMonitor<FeatureFlags> featureMonitor,
ILogger<FeatureService> logger)
{
_featureMonitor = featureMonitor;
_logger = logger;
// 注册变更回调
_featureMonitor.OnChange(OnFeatureFlagsChanged);
}
private void OnFeatureFlagsChanged(FeatureFlags newFlags)
{
_logger.LogInformation("功能开关配置已更新: " +
"NewDashboard={NewDashboard}, BetaFeatures={BetaFeatures}",
newFlags.EnableNewDashboard, newFlags.EnableBetaFeatures);
}
public bool IsFeatureEnabled(string featureName)
{
var flags = _featureMonitor.CurrentValue;
return featureName switch
{
"NewDashboard" => flags.EnableNewDashboard,
"BetaFeatures" => flags.EnableBetaFeatures,
_ => flags.AllowedFeatures.Contains(featureName)
};
}
public int GetMaxItemsPerPage() => _featureMonitor.CurrentValue.MaxItemsPerPage;
}
4.3 在 Controller 中使用热更新配置
[ApiController]
[Route("api/[controller]")]
public class FeaturesController : ControllerBase
{
private readonly IOptionsMonitor<FeatureFlags> _featureMonitor;
public FeaturesController(IOptionsMonitor<FeatureFlags> featureMonitor)
{
_featureMonitor = featureMonitor;
}
[HttpGet]
public IActionResult GetCurrentFeatures()
{
// 每次请求都获取最新的配置
var flags = _featureMonitor.CurrentValue;
return Ok(new
{
flags.EnableNewDashboard,
flags.EnableBetaFeatures,
flags.MaxItemsPerPage,
flags.AllowedFeatures
});
}
[HttpGet("check/{featureName}")]
public IActionResult CheckFeature(string featureName)
{
var flags = _featureMonitor.CurrentValue;
var enabled = featureName switch
{
"NewDashboard" => flags.EnableNewDashboard,
"BetaFeatures" => flags.EnableBetaFeatures,
_ => flags.AllowedFeatures.Contains(featureName)
};
return Ok(new { Feature = featureName, Enabled = enabled });
}
}
4.4 配置热更新的注意事项
// ⚠️ 注意:IOptions 不支持热更新
public class BadService
{
private readonly ApiSettings _settings;
public BadService(IOptions<ApiSettings> options)
{
// 这里的值在应用启动时就固定了,不会更新
_settings = options.Value;
}
}
// ✅ 正确:使用 IOptionsMonitor 支持热更新
public class GoodService
{
private readonly IOptionsMonitor<ApiSettings> _monitor;
public GoodService(IOptionsMonitor<ApiSettings> monitor)
{
_monitor = monitor;
}
public void DoWork()
{
// 每次都获取最新值
var settings = _monitor.CurrentValue;
}
}
第五部分:敏感配置保护(User Secrets、环境变量、Key Vault)
5.1 开发环境:User Secrets
User Secrets 是 .NET 提供的开发环境敏感数据存储机制,数据存储在用户配置目录中,不会进入版本控制。
初始化 User Secrets
# 在项目目录执行
dotnet user-secrets init
# 设置密钥
dotnet user-secrets set "Database:ConnectionString" "Server=localhost;Database=MyApp;Password=MySecretPassword123;"
dotnet user-secrets set "Api:ApiKey" "sk-xxxxxxxxxxxxxxxxxxxxxx"
dotnet user-secrets set "Jwt:Secret" "your-super-secret-key-at-least-32-characters-long"
# 查看所有密钥
dotnet user-secrets list
# 删除密钥
dotnet user-secrets remove "Api:ApiKey"
# 清除所有密钥
dotnet user-secrets clear
User Secrets 配置自动加载
// Program.cs – WebApplication.CreateBuilder 已自动包含 User Secrets
var builder = WebApplication.CreateBuilder(args);
// 在 Development 环境下,User Secrets 会自动加载
// 加载顺序:appsettings.json → appsettings.Development.json → User Secrets → 环境变量
手动添加 User Secrets(控制台应用)
// 对于控制台应用,需要手动添加
var builder = Host.CreateApplicationBuilder(args);
#if DEBUG
builder.Configuration.AddUserSecrets<Program>();
#endif
// 或者根据环境判断
if (builder.Environment.IsDevelopment())
{
builder.Configuration.AddUserSecrets<Program>();
}
5.2 生产环境:环境变量
环境变量是生产环境配置敏感数据的标准方式,特别适合容器化部署。
环境变量命名规则
.NET Core 配置系统支持两种环境变量命名格式:
# 方式一:使用双下划线作为分隔符(推荐)
Database__ConnectionString="Server=prod-db;Database=MyApp;Password=xxx;"
Api__ApiKey="sk-xxxxxxxx"
Features__EnableCache="true"
# 方式二:使用冒号(某些系统不支持)
Database:ConnectionString="Server=prod-db;…"
Docker 环境变量配置
# docker-compose.yml
version: '3.8'
services:
myapp:
image: myapp:latest
environment:
– ASPNETCORE_ENVIRONMENT=Production
– Database__ConnectionString=Server=db;Database=MyApp;User Id=sa;Password=${DB_PASSWORD};
– Api__ApiKey=${API_KEY}
– Jwt__Secret=${JWT_SECRET}
env_file:
– .env.production
# .env.production
DB_PASSWORD=your_database_password
API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxx
JWT_SECRET=your-jwt-secret-key
Kubernetes ConfigMap 和 Secret
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp–config
data:
ASPNETCORE_ENVIRONMENT: "Production"
Database__ConnectionString: "Server=prod-db;Database=MyApp;"
Features__EnableCache: "true"
—
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: myapp–secrets
type: Opaque
stringData:
Database__Password: "your-secret-password"
Api__ApiKey: "sk-xxxxxxxxxxxxxxxxxxxxxx"
Jwt__Secret: "your-jwt-secret"
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
– name: myapp
image: myapp:latest
envFrom:
– configMapRef:
name: myapp–config
– secretRef:
name: myapp–secrets
5.3 企业级:Azure Key Vault
Azure Key Vault 提供了企业级的密钥管理解决方案。
安装 NuGet 包
dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
dotnet add package Azure.Identity
配置 Key Vault
using Azure.Identity;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
var builder = WebApplication.CreateBuilder(args);
// 获取 Key Vault URL(可以从环境变量或配置中读取)
var keyVaultUrl = builder.Configuration["KeyVault:Url"];
if (!string.IsNullOrEmpty(keyVaultUrl))
{
// 使用 DefaultAzureCredential 自动选择认证方式
// 支持以下认证方式(按优先级):
// 1. 环境变量 (AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET)
// 2. Workload Identity (Kubernetes)
// 3. Managed Identity (Azure 资源)
// 4. Visual Studio / VS Code 登录
// 5. Azure CLI 登录
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultUrl),
new DefaultAzureCredential());
}
var app = builder.Build();
Key Vault 密钥命名规范
Key Vault 中的密钥名称需要遵循特定规则:
Key Vault Secret Name → Configuration Key
——————————–|——————
Database–ConnectionString → Database:ConnectionString
Api–ApiKey → Api:ApiKey
Jwt–Secret → Jwt:Secret
使用托管身份(Managed Identity)
// 在 Azure App Service 或 AKS 中使用托管身份
builder.Configuration.AddAzureKeyVault(
new Uri($"https://{keyVaultName}.vault.azure.net/"),
new DefaultAzureCredential(
new DefaultAzureCredentialOptions
{
// 指定租户 ID(多租户场景)
TenantId = builder.Configuration["Azure:TenantId"]
}));
5.4 AWS Secrets Manager(可选方案)
dotnet add package Amazon.Extensions.Configuration.SystemsManager
using Amazon.Extensions.Configuration.SystemsManager;
using Amazon;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddSystemsManager(
source =>
{
source.Path = "/myapp/";
source.Region = RegionEndpoint.APSoutheast1;
source.ReloadAfter = TimeSpan.FromMinutes(15); // 自动刷新
});
5.5 配置优先级总结
优先级从低到高:
1. appsettings.json
2. appsettings.{Environment}.json
3. User Secrets (仅 Development)
4. 环境变量
5. 命令行参数
6. Key Vault / Secrets Manager
最终配置 = 后加载的覆盖先加载的
5.6 敏感配置最佳实践
// ✅ 正确做法:敏感配置使用环境变量或 Key Vault
public class SecureSettings
{
// 非敏感配置可以放在 appsettings.json
public string ApiBaseUrl { get; set; } = string.Empty;
// 敏感配置通过环境变量或 Key Vault 注入
public string ApiKey { get; set; } = string.Empty;
public string ConnectionString { get; set; } = string.Empty;
}
// ❌ 错误做法:敏感信息写入配置文件
{
"Api": {
"ApiKey": "sk-xxxxxxxxxxxx" // 不要这样做!
}
}
// ✅ 正确做法:使用占位符
{
"Api": {
"ApiKey": "${API_KEY}" // 提示需要通过环境变量设置
}
}
第六部分:实战案例:数据库连接字符串动态切换
6.1 场景描述
在实际项目中,我们经常需要根据不同条件动态切换数据库连接:
- 多租户场景:每个租户使用独立的数据库
- 读写分离:读操作连接从库,写操作连接主库
- 分库分表:根据业务数据路由到不同数据库
6.2 完整实现方案
配置定义
// appsettings.json
{
"Database": {
"Default": {
"ConnectionString": "Server=localhost;Database=MyApp;Trusted_Connection=True;",
"CommandTimeout": 30
},
"Master": {
"ConnectionString": "Server=master-db;Database=MyApp;User Id=app;Password=xxx;",
"CommandTimeout": 30
},
"Replica": {
"ConnectionString": "Server=replica-db;Database=MyApp;User Id=app;Password=xxx;",
"CommandTimeout": 60
},
"Tenants": {
"TenantA": "Server=tenant-a-db;Database=TenantA;User Id=app;Password=xxx;",
"TenantB": "Server=tenant-b-db;Database=TenantB;User Id=app;Password=xxx;"
}
}
}
配置类定义
// Models/DatabaseConfig.cs
public class DatabaseConfig
{
public DatabaseConnectionSettings Default { get; set; } = new();
public DatabaseConnectionSettings Master { get; set; } = new();
public DatabaseConnectionSettings Replica { get; set; } = new();
public Dictionary<string, string> Tenants { get; set; } = new();
}
public class DatabaseConnectionSettings
{
public string ConnectionString { get; set; } = string.Empty;
public int CommandTimeout { get; set; } = 30;
}
动态连接字符串解析器
// Services/IDbConnectionResolver.cs
public interface IDbConnectionResolver
{
string GetConnectionString(string? tenantId = null, bool useReplica = false);
int GetCommandTimeout(string? tenantId = null, bool useReplica = false);
}
// Services/DbConnectionResolver.cs
public class DbConnectionResolver : IDbConnectionResolver
{
private readonly IOptionsMonitor<DatabaseConfig> _configMonitor;
private readonly ILogger<DbConnectionResolver> _logger;
public DbConnectionResolver(
IOptionsMonitor<DatabaseConfig> configMonitor,
ILogger<DbConnectionResolver> logger)
{
_configMonitor = configMonitor;
_logger = logger;
}
public string GetConnectionString(string? tenantId = null, bool useReplica = false)
{
var config = _configMonitor.CurrentValue;
// 1. 多租户场景:优先使用租户专属数据库
if (!string.IsNullOrEmpty(tenantId))
{
if (config.Tenants.TryGetValue(tenantId, out var tenantConnStr))
{
_logger.LogDebug("使用租户 {TenantId} 专属数据库", tenantId);
return tenantConnStr;
}
_logger.LogWarning("租户 {TenantId} 未配置专属数据库,使用默认连接", tenantId);
}
// 2. 读写分离场景
if (useReplica && !string.IsNullOrEmpty(config.Replica.ConnectionString))
{
_logger.LogDebug("使用 Replica 数据库连接");
return config.Replica.ConnectionString;
}
// 3. 使用主库连接
if (!string.IsNullOrEmpty(config.Master.ConnectionString))
{
_logger.LogDebug("使用 Master 数据库连接");
return config.Master.ConnectionString;
}
// 4. 使用默认连接
return config.Default.ConnectionString;
}
public int GetCommandTimeout(string? tenantId = null, bool useReplica = false)
{
var config = _configMonitor.CurrentValue;
if (useReplica)
{
return config.Replica.CommandTimeout;
}
return config.Master.CommandTimeout;
}
}
动态 DbContext 工厂
// Data/DynamicDbContextFactory.cs
public interface IDbContextFactory<TContext> where TContext : DbContext
{
TContext CreateDbContext(string? tenantId = null, bool useReplica = false);
}
public class DynamicDbContextFactory : IDbContextFactory<AppDbContext>
{
private readonly IDbConnectionResolver _connectionResolver;
private readonly DbContextOptions<AppDbContext> _baseOptions;
public DynamicDbContextFactory(
IDbConnectionResolver connectionResolver,
DbContextOptions<AppDbContext> baseOptions)
{
_connectionResolver = connectionResolver;
_baseOptions = baseOptions;
}
public AppDbContext CreateDbContext(string? tenantId = null, bool useReplica = false)
{
var connectionString = _connectionResolver.GetConnectionString(tenantId, useReplica);
var timeout = _connectionResolver.GetCommandTimeout(tenantId, useReplica);
var options = new DbContextOptionsBuilder<AppDbContext>(_baseOptions)
.UseSqlServer(connectionString, options =>
{
options.CommandTimeout(timeout);
options.EnableRetryOnFailure();
})
.Options;
return new AppDbContext(options);
}
}
读写分离 Repository 实现
// Repositories/IUserRepository.cs
public interface IUserRepository
{
Task<User?> GetByIdAsync(int id);
Task<IEnumerable<User>> GetAllAsync();
Task<User> CreateAsync(User user);
Task UpdateAsync(User user);
}
// Repositories/UserRepository.cs
public class UserRepository : IUserRepository
{
private readonly IDbContextFactory<AppDbContext> _contextFactory;
public UserRepository(IDbContextFactory<AppDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
// 读操作使用 Replica
public async Task<User?> GetByIdAsync(int id)
{
await using var context = _contextFactory.CreateDbContext(useReplica: true);
return await context.Users.FindAsync(id);
}
public async Task<IEnumerable<User>> GetAllAsync()
{
await using var context = _contextFactory.CreateDbContext(useReplica: true);
return await context.Users.ToListAsync();
}
// 写操作使用 Master
public async Task<User> CreateAsync(User user)
{
await using var context = _contextFactory.CreateDbContext(useReplica: false);
context.Users.Add(user);
await context.SaveChangesAsync();
return user;
}
public async Task UpdateAsync(User user)
{
await using var context = _contextFactory.CreateDbContext(useReplica: false);
context.Users.Update(user);
await context.SaveChangesAsync();
}
}
多租户服务实现
// Services/TenantService.cs
public class TenantService
{
private readonly IDbContextFactory<AppDbContext> _contextFactory;
public TenantService(IDbContextFactory<AppDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public async Task<IEnumerable<Order>> GetTenantOrdersAsync(string tenantId)
{
// 使用租户专属数据库
await using var context = _contextFactory.CreateDbContext(tenantId: tenantId);
return await context.Orders.ToListAsync();
}
public async Task<Order> CreateOrderAsync(string tenantId, Order order)
{
await using var context = _contextFactory.CreateDbContext(tenantId: tenantId);
context.Orders.Add(order);
await context.SaveChangesAsync();
return order;
}
}
服务注册
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 注册配置
builder.Services.Configure<DatabaseConfig>(
builder.Configuration.GetSection("Database"));
// 注册服务
builder.Services.AddSingleton<IDbConnectionResolver, DbConnectionResolver>();
builder.Services.AddSingleton<IDbContextFactory<AppDbContext>, DynamicDbContextFactory>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
// 注册 EF Core 基础配置
builder.Services.AddDbContext<AppDbContext>(options =>
{
// 这里只是基础配置,实际连接字符串由工厂动态设置
options.UseSqlServer("placeholder");
});
var app = builder.Build();
6.3 使用示例
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserRepository _userRepository;
private readonly TenantService _tenantService;
public UsersController(IUserRepository userRepository, TenantService tenantService)
{
_userRepository = userRepository;
_tenantService = tenantService;
}
// 普通查询:自动使用 Replica
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await _userRepository.GetByIdAsync(id);
return user != null ? Ok(user) : NotFound();
}
// 创建操作:自动使用 Master
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
var created = await _userRepository.CreateAsync(user);
return CreatedAtAction(nameof(GetUser), new { id = created.Id }, created);
}
// 多租户查询
[HttpGet("tenant/{tenantId}/orders")]
public async Task<IActionResult> GetTenantOrders(string tenantId)
{
var orders = await _tenantService.GetTenantOrdersAsync(tenantId);
return Ok(orders);
}
}
总结
本文全面介绍了 .NET Core 配置管理的核心概念和实战技巧:
核心要点回顾
| IConfiguration 基础 | 配置源、加载顺序、基本读取方式 |
| 多环境配置 | 环境文件结构、配置合并、环境切换 |
| Options 模式 | IOptions、IOptionsMonitor、IOptionsSnapshot、配置验证 |
| 配置热更新 | 文件监听、变更回调、注意事项 |
| 敏感配置保护 | User Secrets、环境变量、Azure Key Vault |
| 实战案例 | 多租户、读写分离、动态连接字符串 |
最佳实践建议
参考资源
- Microsoft 官方文档 – .NET 中的配置
- ASP.NET Core 中的配置
- Options 模式官方文档
- Azure Key Vault 配置提供程序
- 安全存储开发环境机密
关注引导
如果本文对你有帮助,欢迎:
- 点赞收藏,方便后续查阅
- 关注专栏,获取更多 .NET Core 实战教程
- 评论区留言,分享你的配置管理经验或遇到的问题
作者持续分享 .NET Core、微服务、云原生等技术干货,你的支持是我创作的最大动力!
下一篇预告
【C# .NET Core 实战系列】第 15 篇:全局异常处理实战
预告内容:
- 全局异常处理中间件设计
- 异常过滤器(ExceptionFilter)应用
- 统一错误响应格式封装
- 异常分类处理策略(业务异常 vs 系统异常)
- 异常日志记录与告警
- 生产环境异常处理最佳实践
敬请期待!
标签:C# ASP.NET Core 配置管理 Options模式 多环境配置 Azure Key Vault User Secrets IConfiguration 最佳实践





