ASP. NET Core Automatic Loading of New Configuration after Modifying Configuration File

  • 2021-11-14 05:19:56
  • OfStack

Preface

In the ASP. NET Core default application template, the configuration file is handled as shown in the following code:


config.AddJsonFile(
 path: "appsettings.json",
 optional: true,
 reloadOnChange: true
);
config.AddJsonFile(
 path: $"appsettings.{env.EnvironmentName}.json",
 optional: true,
 reloadOnChange: true
);

Both appsettings. json and appsettings. {env. EnvironmentName}. json configuration files are optional and support for reloading when files are modified.

This feature can be used in ASP. NET Core application to automatically load the modified configuration file without restarting the application after modifying the configuration file, thus reducing the downtime of the system. The steps to implement are as follows:

Injection using configuration API

Suppose you want to inject one configuration type into your program:


public class WeatherOption {
 public string City { get; set; }
 public int RefreshInterval { get; set; }
}

The configuration added in appsettings. json is as follows:


{
 "weather": {
 "city": "GuangZhou",
 "refreshInterval": 120
 }
}

Configure API for injection in the ConfigureServices method of Startup. cs with the following code:


public void ConfigureServices(IServiceCollection services) {
 services.Configure<WeatherOption>(Configuration.GetSection("weather"));
 services.AddControllers();
}

This step is critical, with which API can associate the injected content with the node where the configuration is located. If you are interested in understanding the underlying implementation, you can continue to look at this OptionsConfigurationServiceCollectionExtensions. cs.

Content registered in this way supports automatic reloading when the configuration file is modified.

Load the modified configuration in the controller (Controller)

The controller (Controller) registered in the dependency injection container of the ASP. NET Core application has a life cycle of Scoped, i.e. a new controller instance is created with each request. This only needs to inject IOptionsSnapshot into the constructor of the controller < TOption > Parameter, the code is as follows:


[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase {

 private WeatherOption option;

 public WeatherForecastController(
 IOptionsSnapshot<WeatherOption> options
 ) {
 this.option = options.Value;
 }

 // GET /weatherforcase/options
 [HttpGet("options")]
 public ActionResult<WeatherOption> GetOption() {
 return options;
 }
}

Of course, if you don't want to use this IOptionsSnapshot interface type in the controller (it will bring some refactoring and modifying the existing code, or there is a certain risk), you can add an injection into WeatherOption in ConfigureServices, and the code is as follows:


public void ConfigureServices(IServiceCollection services) {
 services.Configure<WeatherOption>(Configuration.GetSection("weather"));
 //  Add a pair  WeatherOption  The injection,   Life cycle is  Scoped  ,   In this way, new configuration values can be obtained for each request. 
 services.AddScoped(serviceProvider => {
 var snapshot = serviceProvider.GetService<IOptionsSnapshot<WeatherOption>>();
 return snapshot.Value;
 });
 services.AddControllers();
}

This eliminates the need to inject IOptionsSnapshot into the controller < T > Type, the code of the final controller is as follows:


[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase {

 private WeatherOption option;

 public WeatherForecastController(
 WeatherOption option
 ) {
 this.option = option;
 }

 // GET /weatherforcase/options
 [HttpGet("options")]
 public ActionResult<WeatherOption> GetOption() {
 return options;
 }
}

In this way, the controller can load the modified new configuration without modifying any code.

Load the modified configuration in middleware (Middleware)

The life cycle of middleware (Middleware) registered in the dependency injection container of ASP. NET Core application is Singleton, that is, singleton. Only when the application starts, the global instance is created once when the processing connection is created according to the middleware, so it can only be injected by IOptionsMonitor < T > To listen for configuration file modifications, the sample code is as follows:


public class TestMiddleware {

 private RequestDelegate next;
 private WeatherOption option;

 public TestMiddleware(
 RequestDelegate next,
 IOptionsMonitor<WeatherOption> monitor
 ) {
 this.next = next;
 option = monitor.CurrentValue;
 // moni config change
 monitor.OnChange(newValue => {
  option = newValue;
 });
 }

 public async Task Invoke(HttpContext context) {
 await context.Response.WriteAsync(JsonSerializer.Serialize(option));
 }

}

Of course, in the middleware's Task Invoke (HttpContext context) method, IOptionsSnapshot is obtained directly < T > It is also possible, and the code is as follows:


public async Task Invoke(HttpContext context) {
 var snapshot = context.RequestServices.GetService<IOptionsSnapshot<WeatherOption>>();
 await context.Response.WriteAsync(JsonSerializer.Serialize(snapshot.Value));
}

However, doing so seems to deviate from the principle of dependency injection, so it is not recommended.

Summarize


Related articles: