In ASP.NET Core, if you modify the settings in appsettings.json, you will have to restart the site to take effect. Is there a way to refresh and apply it automatically after you modify the settings?

Background

Let's take a look at a website created by ASP.NET Core default templates. It includes two settings files:

  • appsettings.json
  • appsettings.Development.json

The former one is used in production environments, while the latter is used for development environments. In Debug mode, the settings from appsettings.Development.json will be used in proiority.

By default and without explicit code, the CreateWebHostBuilder()method in Program.cs will load both settings files. Commonly, we will create a strong typed class definiation for our settings, like:

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

And use IOptions interface to do Dependency Injection at where you will use the settings.

public Ctor(IOptions<AppSettings> settings)

Problem

In deed, this method can read settings file and have a strong typed usage. However if the appsettings.json file is changed while the website is still running, the settings won't take effect until the website is restarted.

For example, my blog website's title comes from the SiteTitle key in settings, if I login to my server in application runtime, and change the value to "Edi.Wang Test", it will not take effect unless I restart the website.

Solution

Take my blog website for example, I inject IOptions interface in Razor page:

@inject IOptions<AppSettings> Settings

And use the settings in title element:

@Settings.Value.SiteTitle

The solution is very simple, just change the interface to IOptionsSnapshot, if you are injecting settings into Controller, it is also the way to do it.

@inject IOptionsSnapshot<AppSettings> Settings

Let's compare these two interfaces

IOptions

// Summary:
//     Used to retrieve configured TOptions instances.
//
// Type parameters:
//   TOptions:
//     The type of options being requested.
public interface IOptions<out TOptions> where TOptions : class, new()

IOptionsSnapshot

// Summary:
//     Used to access the value of TOptions for the lifetime of a request.
//
// Type parameters:
//   TOptions:
public interface IOptionsSnapshot<out TOptions> : IOptions<TOptions>
where TOptions : class, new()

We can see that IOptionsSnapshot will read the settings per request while IOptions only read the settings once and save it to memory, which is a Singleton pattern.

How about This

I see a lot of solutions on the internet that let you add these into CreateWebHostBuilder()

WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
    config.SetBasePath(Directory.GetCurrentDirectory());
    config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
    config.AddJsonFile("appsettings.Development.json", optional: false, reloadOnChange: true);
    config.AddEnvironmentVariables();
})

This code basically let the developer control which settings file will be loaded when the website starts. For Json file, there is a parameter named reloadOnChange, which indicates if the settings file will be automatically reloaded if the content of this file is changed.

However, this code is not necessary! If you add this code, you still need to restart the website to let your settings changes take effect. The reason is actually the IOptions interface we used before, not this reloadOnChange parameter.

We can do a quick test here. I add a MySettings section in appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "MySettings": {
    "Message": ".NET Core Rocks!"
  },
  "AllowedHosts": "*"
}

Create a strong typed class

public class MySettings
{
    public string Message { get; set; }
}

Configure Services

public void ConfigureServices(IServiceCollection services)
{
    //...
    services.Configure<MySettings>(Configuration.GetSection(nameof(MySettings)));
}

Use in Controller

public class HomeController : Controller
{
    protected IConfiguration Configuration;
    protected MySettings MySettings { get; set; }

    public HomeController(
        IOptions<MySettings> settings = null, 
        IConfiguration configuration = null)
    {
        if (settings != null) MySettings = settings.Value;
        Configuration = configuration;
    }

    public IActionResult Index()
    {
        var m1 = MySettings.Message;
        var m2 = Configuration.GetSection("MySettings")["Message"];
        return Content($"m1:{m1}, m2:{m2}");
    }
}

I didn't write reloadOnChange: true let see the result

Of course they are the same, let's change the settings in runtime:

Refresh the page, only m2 is updated, and this does not require me to set reloadOnChange: true

If we want m1 and m2 both take effect for the settings changes, change to IOptionsSnapshot<MySettings> just like before.

How Come is This?

Our settings actually can be automatically reloaded without explicit written reloadOnChange. Which I can only guess this parameter is by default set to true in ASP.NET Core 2.2. However I didn't find any description on this likely default behaviour on documents. But ASP.NET Core is open source, I can still take a look at the source code.

The ASP.NET Core Extensions repository is here: https://github.com/aspnet/Extensions 

Finally I found that the reloadOnChange is really set to true by default in CreateDefaultBuilder()

The code is in https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Host.cs if you want to take a look by yourselves.

Conclusion

To make strong typed settings changes take effect in runtime in ASP.NET Core 2.2, just use IOptionsSnapshot interface. No need to set reloadOnChange = true.