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
Indeed, 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
.
Anduin
这玩意儿影响不影响性能呢
Erik
Just what I was looking for thanks! I just assumed reload changes worked but never actually tested it until I needed to change a setting. ;-)
Gary
老哥, 你的图片都挂了
Guru
Clearly explained
masterxml
Thanks, it's help a lot.
theMahd
Thanks for sharing. But how about IOptionsMonitor in compare to IOptionsSnapshot?
Augustin
Thanks a lot, very useful !!!
Saad Kamaal
Wow wow wow! What a way to explain and convey the solution. I have been looking for this. Thanks Mate
wk
IOptionsSnapshot is scoped service, not a singleton service, thus cannot inject into singleton service. use IOptionsMonitor<T> instead, refer https://andrewlock.net/creating-singleton-named-options-with-ioptionsmonitor/
Andresb
Thank you! I came to tell you this solved my problem. 👍