IOptionsMonitor无法跟踪变更

编程语言 2026-07-10

我有这段代码,使用的是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);
            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);

            // ################## let's get our value
            var opMonitor = host.Services.GetRequiredService<IOptionsMonitor<MyOptions>>();
            Console.WriteLine(opMonitor.CurrentValue.MyId);

            // ################## and track changes
            opMonitor.OnChange(newValue =>
            {
                Console.WriteLine($"New value: {newValue.MyId}");
            });

            // ################## trying to change the value
            host.Services.GetRequiredService<IConfiguration>().GetSection("Value")["MyId"] = Guid.NewGuid().ToString();

            await Task.Delay(1000);

            // ################## now the new value should be there
            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 . . .

我期望的是:

  • 两个不同的值
  • New value 信息。

有什么想法吗?

解决方案

并非所有配置提供者都需要实现IOptionsMonitor所需的重新加载功能,才能让OnChangeCurrentValue按你期望的方式工作。不幸的是,随AddInMemoryCollection一起提供的MemoryConfigurationProvider并不是内置的、支持该功能的提供者之一。请参阅 文档:

变更通知 仅对基于文件系统的配置提供者提供支持,例如如下所示

Microsoft.Extensions.Configuration.Ini Microsoft.Extensions.Configuration.Json Microsoft.Extensions.Configuration.KeyPerFile Microsoft.Extensions.Configuration.UserSecrets Microsoft.Extensions.Configuration.Xml

创建自定义 IConfigurationSource/IConfigurationProvider 是唯一的解决方案。

基于来自 asp.net core自己的测试 的最小实现,以及 MemoryConfigurationProvider 的源代码,我们可以创建一个类似于现有的 AddInMemoryCollection 的便捷扩展方法 AddReloadableInMemoryCollection

public static class ReloadableMemoryConfigurationBuilderExtensions
{
    public static IConfigurationBuilder AddReloadableInMemoryCollection(this IConfigurationBuilder configurationBuilder) {
        ArgumentNullException.ThrowIfNull(configurationBuilder);

        configurationBuilder.Add(new ReloadableMemoryConfigurationSource());
        return configurationBuilder;
    }

    public static IConfigurationBuilder AddReloadableInMemoryCollection(
        this IConfigurationBuilder configurationBuilder,
        IEnumerable<KeyValuePair<string, string?>>? initialData) {
        ArgumentNullException.ThrowIfNull(configurationBuilder);

        configurationBuilder.Add(new ReloadableMemoryConfigurationSource { InitialData = initialData });
        return configurationBuilder;
    }

    private class ReloadableMemoryConfigurationSource : IConfigurationSource
    {
        public IEnumerable<KeyValuePair<string, string?>>? InitialData { get; set; }

        public IConfigurationProvider Build(IConfigurationBuilder builder) {
            return new ReloadableMemoryConfigurationProvider(this);
        }
    }

    private class ReloadableMemoryConfigurationProvider : ConfigurationProvider
    {
        private readonly ReloadableMemoryConfigurationSource _source;

        public ReloadableMemoryConfigurationProvider(ReloadableMemoryConfigurationSource source) {
            ArgumentNullException.ThrowIfNull(source);

            _source = source;

            if (_source.InitialData != null) {
                foreach (KeyValuePair<string, string?> pair in _source.InitialData) {
                    Data.Add(pair.Key, pair.Value);
                }
            }
        }

        public override void Set(string key, string value) {
            base.Set(key, value);
            OnReload();
        }
    }
}

如果你把代码改为使用它,你就会得到你期望的结果。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章