欢迎光临
我们一直在努力

C#_配置管理实战

【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 → 环境变量 → 命令行

最终配置 = 基础配置 + 环境特定配置(覆盖相同键)

示例说明:

配置项appsettings.jsonappsettings.Development.json最终值(Development 环境)
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: myappconfig
data:
ASPNETCORE_ENVIRONMENT: "Production"
Database__ConnectionString: "Server=prod-db;Database=MyApp;"
Features__EnableCache: "true"


# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: myappsecrets
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: myappconfig
secretRef:
name: myappsecrets

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
实战案例 多租户、读写分离、动态连接字符串

最佳实践建议

  • 优先使用 Options 模式:避免直接使用 IConfiguration,享受类型安全和验证功能
  • 敏感数据不入库:使用 User Secrets(开发)和环境变量/Key Vault(生产)
  • 配置验证前置:使用 ValidateOnStart 在应用启动时发现配置问题
  • 合理使用热更新:功能开关等场景使用 IOptionsMonitor
  • 环境隔离:严格区分开发、测试、生产环境配置

  • 参考资源

    • 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 最佳实践

    赞(0)
    未经允许不得转载:171主机测评 » C#_配置管理实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址