Reputation: 2211
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?
Upvotes: 15
Views: 33030
Reputation: 21224
If you want to have a singleton IHostedService
which is, e.g., 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
}
Upvotes: 1
Reputation: 3265
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>>()));
Upvotes: 19
Reputation: 8672
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.
Upvotes: 8