IOptions<T>不能实时响应appsettings.json变化,因其在启动时只读取一次且不监听文件变更;需改用IOptionsSnapshot<T>(每次请求拉取最新快照)或IOptionsMonitor<T>(支持变更通知与回调)。

为什么 IOptions 不能实时响应 appsettings.json 变化
IOptions<T> 在应用启动时读取一次配置,之后所有注入点拿到的都是同一个实例。即使你手动修改了 appsettings.json 文件并保存,IOptions<T> 不会重新加载——它压根不监听文件变更。
常见错误现象:IOptions<MyConfig> 注入后,改完 JSON 再刷新接口,值还是旧的;有人误以为加了 reloadOnChange: true 就够了,其实那只是告诉 ConfigurationBuilder 启用文件监视,但 IOptions 自身不消费这个能力。
-
IOptions生命周期是 Singleton,只初始化一次 - 要响应变化,必须换用
IOptionsSnapshot<T>或IOptionsMonitor<T> -
reloadOnChange: true是AddJsonFile()的参数,不是IOptions的开关
IOptionsSnapshot 在每次请求中如何获取新配置
IOptionsSnapshot<T> 是 Scoped 生命周期,每次 HTTP 请求(或每个 scope)都会触发一次配置重读(前提是底层 IConfiguration 支持热重载)。
它不是“监听然后推送”,而是“每次构造时按需拉取当前最新快照”。所以你必须确保:配置源(如 appsettings.json)已启用热重载,且 IOptionsSnapshot 是在需要时才解析(比如进 Controller Action 才 resolve)。
- 注册方式和
IOptions完全一样,无需额外配置:services.Configure<MyConfig>(Configuration.GetSection("MyConfig")) - 注入
IOptionsSnapshot<MyConfig>,在任意 Scoped 场景(Controller、Service)中调用.Value即可拿到当前最新值 - 注意:如果在 Singleton 服务里缓存了
IOptionsSnapshot.Value的结果(比如字段赋值),那就又变静态了——必须每次用都访问.Value
IOptionsMonitor 适合什么场景?和 Snapshot 有什么关键区别
IOptionsMonitor<T> 是 Singleton,但能响应配置变更并通知订阅者,适合需要“配置一变就立刻反应”的长期运行逻辑,比如轮询策略调整、连接字符串切换、限流阈值更新。
它底层依赖 IOptionsMonitorCache<T> 和 IOptionsChangeTokenSource<T>,当文件变化时触发 OnChanged 回调。而 IOptionsSnapshot 没有回调机制,只保证“本次 scope 内看到的是最新值”。
-
IOptionsMonitor可以注册多个OnChanged处理器:monitor.OnChanged(config => { /* config 已更新 */ }) -
IOptionsMonitor还提供Get(string name)支持命名选项(Named Options),IOptionsSnapshot不支持 - 性能上:
IOptionsMonitor有少量回调管理开销,但避免了反复解析;IOptionsSnapshot每次都重建T实例(如果T构造开销大,要注意)
配置类里用 static readonly 字段会导致热重载失效
很多人习惯在配置类里写 public static readonly string ApiUrl = "xxx",这会让整个类变成编译期常量绑定——哪怕 IOptionsSnapshot 读到了新值,字段还是旧的。
正确做法是把配置项全部声明为实例属性(public string ApiUrl { get; set; }),且确保配置类是无参可构造的(否则 Configure<> 绑定失败)。
- 不要在配置类里做任何静态初始化、单例引用、IO 操作
- 避免在属性 setter 中加逻辑(如自动补全 URL),因为
Configure是直接反射赋值,不会走 setter - 如果需要派生值(如完整地址),建议用只读属性封装:
public string FullApiUrl => $"{BaseUrl}/api",只要BaseUrl是动态的,它就是活的
配置热重载真正生效的关键不在“用了哪个接口”,而在三点:JSON 文件是否启用 reloadOnChange: true、配置类是否干净无副作用、消费端是否每次都取 .Value 或注册了 OnChanged。漏掉任一环,看起来都像“没刷新”。


















