简体   繁体   中英

Reloading Options with reloadOnChange in ASP.NET Core

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?

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>>()));

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.

If you want to have a singleton IHostedService which is, eg, registered as singleton and the Execute method is triggered repeatedly by a timer + you want a new IOptionsSnapshot on each trigger, you can use the following code:

public MyHostedService (IServiceProvider serviceProvider)
{
    _serviceProvider = serviceProvider;
}

private void ExecutedByTimer ()
{
    using var scope = _serviceProvider.CreateScope();
    var options = scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<MyHostedServiceOptions>>().Value;
    // Voila, options is an up-to-date snapshot
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM