You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

IOptionsMonitor未追踪InMemory配置变更问题求助

问题:InMemory配置修改后未触发Options变更通知

代码实现

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

namespace ConfigGames
{
    internal class MyOptions
    {
        public string? MyId { get; set; }
    }

    internal class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
            builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>()
            {
                ["Value:MyId"] = Guid.NewGuid().ToString()
            });
            builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("Value"));
            builder.Services.AddHostedService<Runner>();
            IHost host = builder.Build();

            var runTask = host.RunAsync();
            
            await Task.Delay(1000);

            // 获取当前值
            var opMonitor = host.Services.GetRequiredService<IOptionsMonitor<MyOptions>>();
            Console.WriteLine(opMonitor.CurrentValue.MyId);

            // 追踪变更
            opMonitor.OnChange(newValue =>
            {
                Console.WriteLine($"New value: {newValue.MyId}");
            });

            // 修改配置值
            host.Services.GetRequiredService<IConfiguration>().GetSection("Value")["MyId"] = Guid.NewGuid().ToString();
            
            await Task.Delay(1000);

            // 期望获取新值
            Console.WriteLine(opMonitor.CurrentValue.MyId);
            
            await Task.Delay(1000);
            await host.StopAsync();
            await runTask;
        }
    }
}

运行输出

Hello, World!
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Projects\ConfigGames\ConfigGames\bin\Debug\net10.0
d1369bd5-31ee-49a4-8443-1edc50b1f38f
d1369bd5-31ee-49a4-8443-1edc50b1f38f
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...

C:\Projects\ConfigGames\ConfigGames\bin\Debug\net10.0\ConfigGames.exe (process 15488) exited with code 0 (0x0).
Press any key to close this window . . .

预期结果

  • 输出两个不同的MyId值
  • 触发OnChange回调,输出“New value: {newValue.MyId}”消息

原因分析

  1. MemoryConfigurationProvider不主动触发变更通知:AddInMemoryCollection添加的默认内存配置源,其底层的MemoryConfigurationProvider没有实现配置变更的发布机制。直接通过IConfiguration索引器修改值,只是更新了内存中的数据,但不会通知配置系统发生了变化。
  2. IOptionsMonitor依赖配置变更事件:IOptionsMonitor的缓存更新和OnChange回调触发,完全依赖配置源发布的变更事件。由于内存配置源没有触发该事件,所以IOptionsMonitor无法感知到值的变化,始终返回初始缓存的配置值。

解决方法

要让InMemory配置支持变更通知,需要手动管理配置源并触发重载事件:

修改后的代码

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

namespace ConfigGames
{
    internal class MyOptions
    {
        public string? MyId { get; set; }
    }

    internal class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
            
            // 创建可手动更新的内存配置源
            var memorySource = new MemoryConfigurationSource
            {
                InitialData = new Dictionary<string, string?>()
                {
                    ["Value:MyId"] = Guid.NewGuid().ToString()
                }
            };
            var memoryProvider = new MemoryConfigurationProvider(memorySource);
            builder.Configuration.Add(memoryProvider);

            builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("Value"));
            builder.Services.AddHostedService<Runner>();
            IHost host = builder.Build();

            var runTask = host.RunAsync();
            
            await Task.Delay(1000);

            // 获取当前值
            var opMonitor = host.Services.GetRequiredService<IOptionsMonitor<MyOptions>>();
            Console.WriteLine(opMonitor.CurrentValue.MyId);

            // 追踪变更
            opMonitor.OnChange(newValue =>
            {
                Console.WriteLine($"New value: {newValue.MyId}");
            });

            // 修改配置值并触发变更通知
            var newMyId = Guid.NewGuid().ToString();
            memoryProvider.Set("Value:MyId", newMyId);
            // 触发配置系统重载,通知所有监听者
            ((IConfigurationRoot)host.Services.GetRequiredService<IConfiguration>()).Reload();
            
            await Task.Delay(1000);

            // 获取更新后的新值
            Console.WriteLine(opMonitor.CurrentValue.MyId);
            
            await Task.Delay(1000);
            await host.StopAsync();
            await runTask;
        }
    }
}

关键修改点

  • 手动创建MemoryConfigurationProvider并保存引用,替代AddInMemoryCollection,这样可以直接操作配置源修改值;
  • 使用memoryProvider.Set()方法更新配置值,确保配置源内部数据同步;
  • 调用IConfigurationRoot.Reload()触发配置变更事件,让IOptionsMonitor感知到变化,更新缓存并执行OnChange回调。

内容的提问来源于stack exchange,提问作者Volker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 13:22:36