在 ASP.NET Core 应用中,appsettings.json 是常用的配置文件。默认情况下,通过 reloadOnChange: true 启用文件变更监控,可以让配置在运行时自动重新加载。
不过,仅仅重新加载配置并不意味着应用各模块会重新启动、刷新状态或使新的配置完全生效。对于某些场景(如重大配置变更、连接字符串切换、权重调整等),你可能希望当配置文件变更时让整个应用或关键模块“重启”或重初始化。
ChangeToken.OnChange 提供了一种在配置变更时注册回调的机制。当 IConfigurationRoot 的变更令牌触发时,你可以在回调中执行自定义逻辑(例如停止应用、启动重启流程等)。
不过,要注意的是:自动重启应用属于高级用法,需要你慎重设计,以避免服务中断、状态丢失、安全或资源问题。
下面我们分步骤探讨如何实现,以及要注意的坑和优化策略。
基础:启用变更监控 + 注册回调
首先,你需要在配置加载阶段开启 reloadOnChange:
builder.Configuration
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
这样 IConfigurationRoot 能够生成变更令牌来监控 JSON 文件修改。
注意:在现代 ASP.NET Core 模板中(使用 CreateDefaultBuilder 或 WebApplication.CreateBuilder),默认就会添加 JSON 配置文件(appsettings.json、appsettings.{Environment}.json)并启用文件变更重载(reloadOnChange = true)。也就是说,这些 JSON 文件作为配置源的一部分,早就被加入 IConfigurationBuilder 管线里。即便你没有在 Program.cs 再写那段 AddJsonFile(...),它也已经被包含了。这个默认行为使得当 JSON 文件发生变动时,会触发内部的变更令牌机制,从而让 ChangeToken.OnChange 能拿到通知。
微软官方文档中提到:默认模板会使用 JSON 配置文件,并带 reloadOnChange 的选项。
所以,如果只是监控 appsettings.json 的话上面的代码可以省略。如果你需要监控一些自定义的json配置文件,可以像上面的代码那样开启 reloadOnChange。
接着,在应用启动后(通常在 Program.cs、WebApplication 构建完成之后)你可以用类似下面的代码订阅变更:
var app = builder.Build();
// 在 app 构建之后
if (builder.Configuration is IConfigurationRoot configRoot)
{
ChangeToken.OnChange(
() => configRoot.GetReloadToken(),
() =>
{
Console.WriteLine("配置文件已变动,触发回调");
// 在这里启动重启流程或其他操作
});
}
在回调里,你就可以编写你希望执行的逻辑,比如优雅停止、重启、重载某些服务等。
如何在变更时重启应用:核心思路与示例
利用 IHostApplicationLifetime.StopApplication()
在 ASP.NET Core 的通用主机模型中,可以通过注入 IHostApplicationLifetime(或新版为 IHostApplicationLifetime)来请求停止当前应用:
var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.StopApplication();
这样主机会开始关闭流程,包括触发 ApplicationStopping、ApplicationStopped 等事件。若宿主环境(例如容器、Windows 服务、IIS 或 systemd)被配置为自动重启应用,那么停止后就会自动启动新的实例。
在 ChangeToken 回调中结合这个方法,基本流程是:
- 在回调中延迟一点时间(防止重复触发)
- 在回调中执行 StopApplication()
- 可选地在回调里启动新的进程(自重启机制)
- 让新的实例接管服务
下面是一个较完整示例(适用于 minimal hosting 的 Program.cs):
var builder = WebApplication.CreateBuilder(args);
// 这部分代码可以省略
builder.Configuration
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
var app = builder.Build();
if (builder.Configuration is IConfigurationRoot configRoot)
{
ChangeToken.OnChange(
() => configRoot.GetReloadToken(),
() =>
{
Console.WriteLine("配置已变更,准备重启");
Task.Run(async () =>
{
await Task.Delay(200); // 简单去抖
// 可以做清理操作:通知模块、断开连接、保存状态等
var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.StopApplication();
// 如果是网站项目,到这里就可以了,再次请求站点会自动启动
// 可选:启动自身新进程(若环境允许)
var exe = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName;
if (!string.IsNullOrEmpty(exe))
{
try
{
System.Diagnostics.Process.Start(exe);
}
catch (Exception ex)
{
Console.WriteLine($"启动新进程失败:{ex.Message}");
}
}
});
});
}
app.MapGet("/", () => "OK");
app.Run();
在这个示例里:
- 延迟 200ms 简单去抖,避免因文件写入的多个通知导致重复重启
- 调用 StopApplication() 请求优雅关闭
- 尝试启动新的进程(如果可行)作为自启动机制
借助外部宿主重启机制
在实际生产环境中,最可靠的方式通常是将“重启责任”交给宿主环境:
- 在容器(Docker / Kubernetes)中设置 restartPolicy
- 在 systemd、Supervisor、Windows 服务中设置“服务失败重启”策略
- 在 IIS 或 Azure 等平台配置健康探测 / 应用自动重启
这样一旦应用停止(通过 StopApplication()),宿主会自动启动一个新的实例,而你的应用本身无需自行 Process.Start。
这种方式通常更稳定、更安全,也更符合操作惯例。
为何 ChangeToken.OnChange 可能被触发多次?如何防抖?
一个常见的问题是:即使你修改 appsettings.json 只有一次,也可能触发 OnChange 回调次数为 2 次甚至更多。原因如下:
- 文件监控机制(例如 FileSystemWatcher)可能在一次文件写入操作中发出多个事件(修改、关闭、创建、重命名等)。
- 底层 IFileProvider 对同一变更可能多次通知 IChangeToken。
- 如果你用多个 AddJsonFile 注册多个 JSON 文件,变更可能触发多个来源的通知。
- 如果你在回调中写回文件或修改配置,也可能引起“自触发”变更回调。
为了避免重复的重启或重复逻辑执行,你需要在回调中做去重 / 防抖处理。常见策略包括:
- 延迟执行 + 取消前一个延迟(Debounce)
- 对比哈希值 / 版本号,只有内容真正改变才执行
- 使用一次性订阅 + 回调内重新注册
- 使用标志变量防止重叠执行
例如:
bool isHandling = false;
ChangeToken.OnChange(
() => configRoot.GetReloadToken(),
() =>
{
if (isHandling) return;
isHandling = true;
Task.Run(async () =>
{
await Task.Delay(200);
// 对比哈希或时间戳判断是否真正变更
lifetime.StopApplication();
isHandling = false;
});
});
这样就可以避免因多次通知而多次触发重启。
适用场景、优点与风险提醒
适用场景
- 开发 / 测试环境:希望修改配置后立即生效且无需重启整个应用
- 运维工具 / 内部后台系统:配置变更时重启服务可能被接受
- 小型服务 / 无状态服务:重启引发的中断影响较小
优点
- 修改配置文件即可触发应用更新,无需人工重启
- 对于某些配置变更(例如连接字符串、日志级别、开关控制等)可以即时生效
- 能在回调中插入清理、备份、通知等流程
风险与限制
- 服务中断:重启过程中会有短暂不可用窗口
- 状态丢失:如果应用持有状态(In-Memory 缓存、会话、连接等),重启时可能丢失
- 权限 / 环境限制:启动新进程或重启机制在某些宿主环境可能被禁止
- 无限重启 / 重入风险:若配置频繁变更或回调引起二次变更,可能导致反复重启
- 资源释放与异常处理:必须在停止流程中优雅释放连接、事务、线程等资源
- 不可靠在所有环境中:部分宿主环境可能对 StopApplication 无响应或无法自动重启
总结建议
- 使用 ChangeToken.OnChange + IHostApplicationLifetime.StopApplication() 是一种可行的“在配置变更时重启”方式,但最好作为一种备选方案,而不是常规机制
- 更稳妥、更推荐的做法是:让宿主层处理重启(例如容器或服务平台自动重启)
- 在回调中加入去抖 / 重复判断逻辑,避免多次触发
- 若可能,尽量使用动态重载、模块刷新或 IOptionsMonitor<T> 来处理配置变更,而不是重启整个应用
- 在编写回调逻辑时,注意异常处理、资源释放、中断控制等细节