Cannot use IOptionsMonitor to detect changes in ASP.NET Core
Asked Answered
U

4

6

I am working on Asp.Net Core app

I want to change the configuration settings after running the application

I am using IOptionsMonitor, but it is not detecting changes

In Startup.cs -> Configuration() method I have

services.Configure<Config>(Configuration.GetSection("someConfig"));

In a different class where these config settings are read, I wrote something like

var someConfig= serviceProvider.GetRequiredService<IOptionsMonitor<Config>>();

But when I change the configuration file (Json File), the change is not detected, and someConfig does not change.

Config POCO class:

public class Config
{
    public string name {get; set;}
    //More getters and setters
}

Edit:

services.AddSingleton<ConfigHelpers>;

I am using a singleton object in which I am trying to read the config. It works fine if its not a snigleton. Is there a way to change the config even in a singleton object ?

in ConfigHelpers.cs

var someConfig= serviceProvider.GetRequiredService<IOptionsMonitor<Config>();

since it is defined as singleton in Startup.cs, changes made to Config are not reflected.

Unfounded answered 23/3, 2018 at 16:18 Comment(6)
Did you set the reloadChanges flag to "true" for your settings file?Nipha
@SimplyGed yes I did.Unfounded
@Unfounded Could you replace IOptionsMonitor with IOptionsSnapshot?Repair
@win Tried it, its not working. I am trying to open the config JSON file and change it manually (Save -> close). Is this how it works ?Unfounded
Have you tired to change AddSingleton to AddScoped? If that helps, then I would change the way you are using ConfigHelper in your project.Strake
@Strake It works with Scoped and Transient, But I need to use Singleton.Unfounded
E
8

Did you create your WebHostBuilder like this aprox:

var config = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("config.json", optional: false, reloadOnChange: true)
    .AddEnvironmentVariables()
    .Build();

var AppConfig = config.Get<AppConfig>();

var host = new WebHostBuilder()
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseUrls("http://*:" + (AppConfig.Port ?? 80))
    .UseKestrel()
    .UseIISIntegration()
    // Clones your config values
    .UseConfiguration(config)
    .UseStartup<Startup>()
    .Build();

Then it will clone your values and you'll loose the live reload ability.

You'll have to use ConfigureAppConfiguration and do the following:

var host = new WebHostBuilder()
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseUrls("http://*:" + (AppConfig.Port ?? 80))
    .UseKestrel()
    .ConfigureAppConfiguration((builder, conf) =>
    {
        conf.SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("config.json", optional: false, reloadOnChange: true)
            .AddEnvironmentVariables();
    })
    .UseIISIntegration()
    //.UseConfiguration(config)
    .UseStartup<Startup>()
    .Build();
Epanodos answered 29/10, 2018 at 16:3 Comment(1)
Thank you very much, this worked perfectly for me :)Masseuse
K
2

To detect configuration changes, a listener must be registered to the IOptionsMonitor by using its OnChange method.

Example on a BackgroundService:

// ...
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Threading;

public class MySingletonService : BackgroundService
{
    private IDisposable _optionsChangedListener;
    private MyOptions _myCurrentOptions;

    public MySingletonService(IOptionsMonitor<MyOptions> optionsMonitor)
    {
        _optionsChangedListener = optionsMonitor.OnChange(MyOptionsChanged);
        _myCurrentOptions = optionsMonitor.CurrentValue;
    }

    private void MyOptionsChanged(MyOptions newOptions, string arg2)
    {
        _myCurrentOptions = newOptions;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            Console.WriteLine(_myCurrentOptions.MyProperty);
            await Task.Delay(1000, stoppingToken);
        }
    }

    public override void Dispose()
    {
        _optionsChangedListener.Dispose();
        base.Dispose();
    }
}
public class MyOptions
{
    public const string SectionKey = "MyOptions";

    public string MyProperty { get; set; }
}
Kislev answered 27/9, 2022 at 15:53 Comment(1)
Thank you very much! I was really having a hard time troubleshooting this with a BackgroundService and the example you gave was crystal clear and I was able to resolve my issue.Ellaelladine
S
1

After a long day with .NET Core 3.1 reading the docs, various blog and forum posts and implementing all the suggested variations of IOptionsMonitor, IOptions, IOptionsSnapshot, IConfiguration, IServiceCollection and IServiceProvider, the only way I could get a dynamic update to work with the JsonConfigurationProvider (appsettings.json) and ConfigureWebHostDefaults() was this rather ugly workaround that could be implemented in a singleton as per the question:

    /// <summary>
    /// Reference to the application IConfiguration implementation
    /// </summary>
    static IConfiguration configuration;

    /// <summary>
    /// Reference buffer for Configuration property
    /// </summary>
    static AppConfiguration appConfiguration = new AppConfiguration();

    /// <summary>
    /// Access to the current configuration
    /// </summary>
    public static AppConfiguration Configuration
    {
        get
        {
                configuration.Bind("AppConfiguration", appConfiguration);
                return appConfiguration;
        }
    }
Soothfast answered 30/4, 2020 at 7:36 Comment(0)
I
0

And for noobs like myself, maybe I can save you a little time by pointing out that you need to update the appsettings.json in your build directory, not the one in your project directory, to be able to see changes at runtime.

Took me a while to figure out that I was not running into an ASP / .Net issue, but that instead it was a classic PEBCAK.

PS: I used the (newer) WebApplication.CreateBuilder(args) that already configures a lot of default stuff. And configured options using the method in the original post:

builder.Services.Configure<Config>(Configuration.GetSection("someConfig"));

I just needed to use an injected IOptionsMonitor<Config> in my (background) service with no need to include an OnChange handler. Everything worked fine.

Iowa answered 15/10, 2024 at 9:39 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.