Reloading Options with reloadOnChange in ASP.NET Core

15,839

Solution 1

You need to inject IOptionsSnapshot<AppSettings> to get the reload working.

Unfortunately you cannot load the IOptionsSnapshot into a Singleton service. IOptionsSnapshot is a Scoped service so you can only reference it in a Scoped or Transient registered class.

But, if think about it, that makes sense. The settings need to be reloaded when they change so if you inject them into a Singleton then the class will never get the updated settings because the constructor will not be called again for a Singleton.

Solution 2

What you can do is create your wrapper class around the config class like you did in AppSettingsWrapper and inject IOptionsMonitor. Then keep a private property of your settings class. That wrapper can be injected as a singleton and the IOptionsMonitor will keep track of your changes.

public class AppSettingsWrapper
{
    private AppSettings _settings;

    public AppSettingsWrapper(IOptionsMonitor<AppSettings> settings)
    {
        _settings = settings.CurrentValue;

        // Hook in on the OnChange event of the monitor
        settings.OnChange(Listener);
    }

    private void Listener(AppSettings settings)
    {
        _settings = settings;
    }

    // Example getter
    public string ExampleOtherApiUrl => _settings.ExampleOtherApiUrl;
}

Then register your wrapper class as a singleton

services.AddSingleton(sp => new AppSettingsWrapper(sp.GetService<IOptionsMonitor<AppSettings>>()));
Share:
15,839

Related videos on Youtube

Shamshiel
Author by

Shamshiel

Updated on June 04, 2022

Comments

  • Shamshiel
    Shamshiel almost 2 years

    In my ASP.NET Core application I bind the appsettings.json to a strongly typed class AppSettings.

    public Startup(IHostingEnvironment environment)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(environment.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables();
    
        Configuration = builder.Build();
    }
    
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<AppSettings>(Configuration);
        //...
    }
    

    In a singleton class I wrap this AppSettings class like this:

    public class AppSettingsWrapper : IAppSettingsWrapper
    {
        private readonly IOptions<AppSettings> _options;
    
        public AppSettingsAdapter(IOptions<AppSettings> options)
        {
            _options = options ?? throw new ArgumentNullException("Options cannot be null");
        }
    
        public SomeObject SomeConvenienceGetter()
        {
            //...
        }
    }
    

    Now I'm struggling with reloading the AppSettings if the json file changes. I read somewhere that the class IOptionsMonitor can detect changes but it doesn't work in my case.

    I tried calling the OnChange event like this for testing purposes:

    public void Configure(IApplicationBuilder applicationBuilder, IOptionsMonitor<AppSettings> optionsMonitor)
    {
        applicationBuilder.UseStaticFiles();
        applicationBuilder.UseMvc();
    
        optionsMonitor.OnChange<AppSettings>(vals => 
        {
            System.Diagnostics.Debug.WriteLine(vals);
        });
    }
    

    The event is never triggered when I change the json file. Has someone an idea what I can change to get the reloading mechanic to work in my scenario?

  • Shamshiel
    Shamshiel almost 6 years
    I thought that I maybe could rebind the AppSettingsWrapper with the new AppSettings in the "OnChange" event. It is unfortunate that I have to change the lifetime of my wrapper class because besides the rare reloading it never changes.
  • Simply Ged
    Simply Ged almost 6 years
    Unless you have a really compelling reason to keep it as a singleton e.g. real time performance, switching to a Scoped service should give you what you want without much penalty, after all you are still injecting the same class, only the registration has changed.
  • heymega
    heymega over 5 years
    Nothing stopping you creating a scope inside your singleton using IServiceProvider.CreateScope.
  • jjxtra
    jjxtra over 4 years
    What is the purpose of reloadOnChange if it doesn't ping the change handles when the config source (in this case a file) changes?
  • C-F
    C-F over 2 years
    You completely ignore IOptionsMonitor in your answer. According to the documentation, this one is supposed to be able to detect changes being a singleton. Perfect! Would be... if it worked...