15

I'm using ASP.NET Core 2.0 and I have configuration code like this in the Main method:

public static void Main(string[] args)
{
    var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
    var configuration = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{environment ?? "Production"}.json", optional: true, reloadOnChange: true)
        .AddEnvironmentVariables()
        .AddCommandLine(args)
        .Build();
}

I have the reloadOnChange set to true, and in my controller I am using IOptionsSnapshot;

public HomeController(ILogger<HomeController> logger, IOptionsSnapshot<AppSettings> options)

But when I modify the values in my appsettings.json, I have to restart my app every time or the changes are not being picked up just by refreshing the browser. What am I doing wrong?

I've tried to run the app both with console and IIS Express; I've also tried IOptionsMonitor, same thing. What is the difference between IOptionsMonitor and IOptionsSnapshot?

Dave Anderson
  • 11,836
  • 3
  • 58
  • 79
Ray
  • 12,101
  • 27
  • 95
  • 137

1 Answers1

48

As mentioned in the documentation, just enabling reloadOnChange and then injecting IOptionsSnapshot<T> instead of IOptions<T> will be enough. That requires you to have properly configured that type T though. Usually a configuration registration will look like this:

services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

However, looking closer at your code, it does not seem that you are using the new ASP.NET Core 2.0 way of configuring your program. The configuration is now part of dependency injection, so you will set it up as part of the WebHostBuilder, using ConfigureAppConfiguration. That could for example look like this:

public static IWebHost BuildWebHost()
    => new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .ConfigureAppConfiguration((builderContext, config) =>
        {
            IHostingEnvironment env = builderContext.HostingEnvironment;

            config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
            config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
        })
        .UseStartup<Startup>()
        .Build();

If you are using the default builder using WebHost.CreateDefaultBuilder(), then you don’t even need to do this, as the configuration is then automatically set up like that with reloadOnChange activated.


The difference between IOptionsSnapshot and IOptionsMonitor is that the IOptionsSnapshot will just give you a snapshot of the options at the time the IOptionsSnapshot<T> object is being constructed.

That’s why the usage is exactly the same as with IOptions<T>: You inject it in the constructor and then store the options.Value in the instance to access the options later. At that point, that object is fixed and will never change. It’s just that the IOptionsSnapshot<T> is registered as a scoped dependency instead of a singleton dependency like IOptions<T>, so it gets the chance to get the current configuration values on every request instead of just once.

The IOptionsMonitor<T> however is a singleton service that allows you to retrieve the current configuration value at any given time. So it is especially useful for singleton services that need to get the current configuration whenever they need it. In addition, the options monitor offers a push mechanism to get notified of configuration changes by the configuration sources. That way, you can explicitly handle configuration changes.

Options snapshots are designed to be used for transient or scoped dependencies, so you will be fine just using those most of the time. Only in rare occasions when you have to use a singleton service that also needs to have the most updated configuration, you should have the need to use an options monitor. In those cases, note that just switching from snapshots to a monitor will not be enough. Usually, you will have to handle changed configuration in some way (for example clean up state, clear caches etc.). So you should always think about whether you actually need reloadable configuration for everything or if just restarting the application isn’t a viable alternative.

poke
  • 369,085
  • 72
  • 557
  • 602
  • Thank you poke for your answer! I updated my configuration code like yours and the AppSettings in my controller worked. However I have this code in a middleware constructor that is still not pick up changes, public MyMiddleware(RequestDelegate next, IOptionsSnapshot options, ILoggerFactory loggerFactory), do you know if middleware should react to configuration changes as controllers would? – Ray Oct 04 '17 at 17:14
  • 3
    @fanray Yeah, middleware is unfortunately a different thing. Middleware is set up *once* when the application runs and the `Startup.Configure` method is executed. So all dependencies are resolved at that time, and the middleware instances are kept around as shared instances. So you cannot inject the options there. You will have to resolve the options within the `Invoke` method then, for example using `context.RequestServices.GetRequiredService>()`. – poke Oct 04 '17 at 18:00
  • Hi @poke I got it working in my middleware with var settings = context.RequestServices.GetService>().Value; would this GetService code affect performance if every request comes into this middleware? And finally, is there a way to get the AppSettings object in Startup's ConfigureServices method? Thank you! – Ray Oct 04 '17 at 20:07
  • No, the `GetService` call should be pretty much the same thing as injecting it into a constructor. It’s just that DI is architecturally better compared to `GetService` calls (which is called “service locator pattern”). But sometimes, you just cannot avoid it. Btw. if you find yourself requesting more than one service, consider creating a handler service which you then then request in the middleware. That handler service can then use normal DI to access stuff (including options snapshots). – poke Oct 04 '17 at 20:22
  • As for accessing the options within `ConfigureServices`, no, that’s not really possible since `ConfigureServices` is to set up the dependencies, so you cannot request them at that time. So you would have to use the startup’s `Configuration` property to access your settings that way. But you very likely won’t need this: Check out [this article by Andrew Lock](https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/) on how to use DI for configuring things in `ConfigureServices`. – poke Oct 04 '17 at 20:25
  • 1
    You don't have to use `GetRequiredService` inside the `Invoke` method in middleware since the `Invoke` method itself can request dependencies, right @poke? – ssmith Dec 05 '17 at 15:10
  • @ssmith Yes, you’re right, dependencies will be automatically provided to the `Invoke` method. – poke Dec 05 '17 at 15:40
  • What about a singleton that takes it's configuration once? How to notify of reload then? Trying to get a change token from IConfiguration does not call back properly... – jjxtra Sep 16 '19 at 23:51
  • @jjxtra When consuming options from a singleton service while wanting to support reloading configuration, your best option would be to directly use the `IOptionsMonitor` to retrieve the options whenever you need to use them (using `monitor.CurrentValue` or `monitor.Get(name)`). Usually that will also allow you to avoid the change tokens altogether. – poke Sep 17 '19 at 01:32