在现代 .NET Core 开发中,配置与选项系统早已脱离了传统 App.config 的单一模式,形成了一套支持多源混合、强类型绑定、动态更新、可高度扩展的完整体系。
1. 获取配置与选项
所有配置的读取、合并、管理都围绕 IConfiguration 对象的 Providers 集合展开,系统会把所有注册的配置源统一扁平化管理,对外提供一致的访问入口。
前置准备步骤
新建一个 .NET Core 控制台或 Web 项目,在项目根目录添加名为 appsettings.json 的文件。
右键点击该文件,选择"属性 ",将"复制到输出目录 "设置为"始终复制",避免运行时找不到配置文件。
通过 NuGet 包管理器安装两个核心依赖:Microsoft.Extensions.Configuration.Json 和 Microsoft.Extensions.Configuration.Binder。
编写基础构建代码,创建 ConfigurationBuilder 实例,注册 JSON 配置源并构建配置根对象:
cs
using Microsoft.Extensions.Configuration;
// 1. 创建配置构建器
IConfigurationBuilder configBuilder = new ConfigurationBuilder();
// 2. 注册JSON配置源,reloadOnChange开启后文件修改自动重载
configBuilder.AddJsonFile(
path: "appsettings.json",
optional: false,
reloadOnChange: true
);
// 3. 构建配置根对象
IConfigurationRoot configRoot = configBuilder.Build();
IConfiguration 常用方法与属性列表
|------------------------|-------------------------|----------------------------------------------------|
| 成员名称 | 作用说明 | 代码示例 |
| thisstring key | 索引器,直接读写配置键值 | var siteName = configRoot"SiteName"; |
| GetSection(string key) | 获取指定层级的子配置节点,支持冒号分隔嵌套路径 | var dbSection = configRoot.GetSection("Database"); |
| GetChildren() | 获取当前节点下的所有子配置项 | foreach(var child in configRoot.GetChildren()) |
| GetReloadToken() | 获取配置变更通知令牌,用于自定义监听逻辑 | var token = configRoot.GetReloadToken(); |
| Providers | 获取所有已注册的配置提供器集合 | foreach(var provider in configRoot.Providers) |
| Reload() | 强制重新加载所有配置源的数据 | configRoot.Reload(); |
2. 使用强类型对象承载配置
直接通过字符串键读取配置很容易出现拼写错误、类型转换异常,而强类型绑定可以自动把配置映射到普通 POCO 类上,实现类型安全的配置访问。
步骤与代码
在 appsettings.json 中添加嵌套结构的测试配置:
bash
{
"SiteName": "我的博客站点",
"MaxPageSize": 50,
"Database": {
"ConnectionString": "Server=localhost;Database=BlogDB;",
"Timeout": 30
}
}
定义对应结构的普通 POCO 类,不需要继承任何基类:
cs
public class AppSettings
{
public string SiteName { get; set; }
public int MaxPageSize { get; set; }
public DatabaseSettings Database { get; set; }
}
public class DatabaseSettings
{
public string ConnectionString { get; set; }
public int Timeout { get; set; }
}
执行自动绑定,直接生成完整的强类型配置对象:
cs
var appSettings = new AppSettings();
configRoot.GetSection("AppSettings").Bind(appSettings);
// 直接访问强类型属性,无需手动类型转换
Console.WriteLine($"站点名称:{appSettings.SiteName}");
Console.WriteLine($"数据库连接串:{appSettings.Database.ConnectionString}");
3. 使用环境变量配置源
环境变量是容器化、云原生部署中最常用的配置注入方式,可以把密钥、连接串等敏感配置和代码文件完全隔离,避免敏感信息泄露。
步骤与代码
首先通过 NuGet 安装 Microsoft.Extensions.Configuration.EnvironmentVariables 包。
在配置构建流程中添加环境变量配置源:
cs
configBuilder.AddEnvironmentVariables();
测试嵌套配置:系统会自动把环境变量中的双下划线 __ 转换为配置层级的冒号,比如设置环境变量 AppSettings__SiteName="生产环境站点",程序读取时就会自动映射为 AppSettings:SiteName 配置项,环境变量的优先级默认高于 JSON 文件,会自动覆盖本地配置。
4. 使用命令行配置源
命令行配置源适合在应用启动时临时覆盖配置,不需要修改任何配置文件,非常适合容器启动脚本、临时调试场景。
步骤与代码
安装 NuGet 包 Microsoft.Extensions.Configuration.CommandLine。
在配置构建流程中添加命令行配置源,同时支持自定义短参数映射:
cs
// 定义短参数映射,比如 -p 对应 --Port
var switchMappings = new Dictionary<string, string>()
{
["-p"] = "Port",
["-e"] = "Environment"
};
configBuilder.AddCommandLine(args, switchMappings);
启动应用时传入参数即可覆盖配置,示例启动命令:
MyApp.exe --Port 8080 --Environment Production
也可以使用短参数
MyApp.exe -p 8080 -e Production
5. 配置管理器 ConfigurationManager
从 .NET 6 开始新增的 ConfigurationManager 把配置构建、服务托管、环境管理能力整合在一起,大幅简化了应用启动流程,是现代 .NET 项目的默认配置方式。
在默认创建的 .NET 6+ Web 项目中,你不需要手动创建 ConfigurationBuilder,框架已经预初始化好了 ConfigurationManager:
cs
var builder = WebApplication.CreateBuilder(args);
// 这里的 builder.Configuration 就是 ConfigurationManager 实例
// 它已经自动按优先级加载了:appsettings.json → appsettings.{环境}.json → 环境变量 → 命令行
var siteName = builder.Configuration["AppSettings:SiteName"];
// 你可以继续添加自定义配置源
builder.Configuration.AddJsonFile("customConfig.json", optional: true);
6. 选项模式
选项模式是 .NET Core 官方推荐的最佳实践,通过依赖注入把配置托管到服务容器中,实现配置与业务逻辑的完全解耦。
6.1 将配置绑定Options对象
先在项目中引入依赖注入相关包,在 Program.cs 中完成注册:
cs
builder.Services.Configure<AppSettings>(
builder.Configuration.GetSection("AppSettings")
);
在业务服务中通过依赖注入获取选项对象:
cs
public class BlogService
{
private readonly AppSettings _settings;
public BlogService(IOptions<AppSettings> settings)
{
_settings = settings.Value;
}
public void PrintConfig()
{
Console.WriteLine(_settings.SiteName);
}
}
6.2 命名选项的使用
当需要同一配置类的多组不同配置时,命名选项可以避免重复定义相似的配置类:
cs
// 注册两组不同名称的数据库配置
builder.Services.Configure<DatabaseSettings>("Primary",
builder.Configuration.GetSection("Database:Primary"));
builder.Services.Configure<DatabaseSettings>("Replica",
builder.Configuration.GetSection("Database:Replica"));
// 使用时通过名称获取指定配置
public class DbFactoryService
{
public DbFactoryService(IOptionsSnapshot<DatabaseSettings> dbOptions)
{
var primaryDb = dbOptions.Get("Primary");
var replicaDb = dbOptions.Get("Replica");
}
}
6.3 Options内容验证
在应用启动阶段就自动验证配置合法性,避免运行时才发现配置错误导致崩溃:
cs
// 先给选项类添加数据注解特性
public class AppSettings
{
[Required(ErrorMessage = "站点名称不能为空")]
public string SiteName { get; set; }
[Range(10, 100, ErrorMessage = "分页大小必须在10-100之间")]
public int MaxPageSize { get; set; }
}
// 注册选项时开启数据注解验证
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"))
.ValidateDataAnnotations();
6.4 Options后期配置
后期配置会在所有配置源加载完成后最后执行,适合补充默认值、联动调整配置:
cs
builder.Services.PostConfigure<AppSettings>(options =>
{
// 所有配置源加载完成后,自动给站点名称加上环境后缀
options.SiteName += $" - {builder.Environment.EnvironmentName}";
});
6.5 IOptionsSnapshot
Scoped 生命周期的选项快照,支持配置热更新,同一个请求内配置保持一致,新请求自动读取最新配置:
cs
public class HomeController : Controller
{
private readonly IOptionsSnapshot<AppSettings> _snapshotSettings;
public HomeController(IOptionsSnapshot<AppSettings> snapshotSettings)
{
_snapshotSettings = snapshotSettings;
}
public IActionResult Index()
{
// 修改appsettings.json后,刷新页面就能拿到新配置,无需重启应用
return Content(_snapshotSettings.Value.SiteName);
}
}
6.6 IOptionsMonitor
单例生命周期的配置监控器,可以注册回调监听配置变更事件:
cs
var app = builder.Build();
var monitor = app.Services.GetRequiredService<IOptionsMonitor<AppSettings>>();
// 配置变化时自动触发回调
monitor.OnChange(newSettings =>
{
Console.WriteLine($"配置已更新,新站点名称:{newSettings.SiteName}");
});
7. 设置一个配置中心
当应用规模扩展到分布式集群时,分散在每台机器的配置文件维护成本极高,自定义配置中心可以实现配置的集中管理和实时推送。
7.1 什么是配置中心
配置中心是独立于业务应用的集中式配置服务,所有应用实例统一从服务端拉取配置,支持配置的集中修改、版本回溯、灰度发布,修改一次配置就能同步到所有节点,无需逐个登录服务器修改文件。
7.2 ConfigurationSource
自定义配置源类,用来承载配置中心的连接参数:
cs
public class CustomConfigSource : IConfigurationSource
{
public string ServerUrl { get; set; }
public string AppId { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new CustomConfigProvider(this);
}
}
7.3 ConfigurationProvider
核心配置提供器,负责从远程配置中心拉取数据:
cs
public class CustomConfigProvider : ConfigurationProvider
{
private readonly CustomConfigSource _source;
public CustomConfigProvider(CustomConfigSource source)
{
_source = source;
}
// 重写Load方法,从远程接口拉取配置
public override void Load()
{
using var httpClient = new HttpClient();
var json = httpClient.GetStringAsync($"{_source.ServerUrl}/pull?appId={_source.AppId}").Result;
Data = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
}
}
7.4 配置扩展方法
封装扩展方法,让自定义配置源接入更简洁:
cs
public static class CustomConfigExtensions
{
public static IConfigurationBuilder AddCustomConfigCenter(
this IConfigurationBuilder builder,
string serverUrl,
string appId)
{
return builder.Add(new CustomConfigSource
{
ServerUrl = serverUrl,
AppId = appId
});
}
}
接入时只需要一行代码:
cs
configBuilder.AddCustomConfigCenter("http://localhost:5000/config", "MyBlogApp");
7.5 配置动态更新(HTTP Long Polling方案)
在配置提供器中加入长轮询逻辑,实现配置准实时推送,这也是 Nacos、Apollo 等主流配置中心的核心实现方案:
cs
public class CustomConfigProvider : ConfigurationProvider
{
private Timer _pollTimer;
private string _currentVersion = "0";
public CustomConfigProvider(CustomConfigSource source)
{
_source = source;
// 后台启动长轮询任务
_pollTimer = new Timer(StartLongPolling, null, 0, Timeout.Infinite);
}
private async void StartLongPolling(object state)
{
using var httpClient = new HttpClient();
// 设置90秒长轮询超时,和主流配置中心默认超时保持一致
httpClient.Timeout = TimeSpan.FromSeconds(90);
while(true)
{
try
{
// 带着当前版本号发起请求,服务端没有新配置就挂起连接
var response = await httpClient.GetAsync(
$"{_source.ServerUrl}/longpoll?appId={_source.AppId}&version={_currentVersion}"
);
if(response.IsSuccessStatusCode)
{
var configData = await response.Content.ReadFromJsonAsync<ConfigResponse>();
// 配置有更新,重新加载
Data = configData.Configs;
_currentVersion = configData.Version;
// 触发配置系统重载通知
OnReload();
}
}
catch(Exception ex)
{
// 长轮询超时或出错,等待1秒后重试
await Task.Delay(1000);
}
}
}
}
服务端侧的长轮询核心逻辑可以借助 .NET Core 的 Channel 实现,配置变更时直接通知等待的请求,无需轮询数据库,实现准实时推送效果。