Preface
The program plays an indispensable role in the running of the configuration file; usually, when using visual studio to create a project, the project configuration file will be automatically generated in the project root directory, such as , or is widely used by everyone.appsettings.{}.json;
Configuration File
As an entrance, we can intervene and adjust the program without updating the code, so a comprehensive understanding of its loading process is very necessary.
When is the default configuration file loaded
In the file, view the following code
public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => (args) .UseStartup<Startup>(); }
Located in the assembly
Inside, when the program is executed(args)
When the default configuration file is loaded inside the CreateDefaultBuilder method
The code is as follows
public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder(); if ((())) { (()); } if (args != null) { (new ConfigurationBuilder().AddCommandLine(args).Build()); } ((builderContext, options) => { (("Kestrel")); }) .ConfigureAppConfiguration((hostingContext, config) => { var env = ; ("", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{}.json", optional: true, reloadOnChange: true); if (()) { var appAssembly = (new AssemblyName()); if (appAssembly != null) { (appAssembly, optional: true); } } (); if (args != null) { (args); } }) .ConfigureLogging((hostingContext, logging) => { (("Logging")); (); (); (); }) .ConfigureServices((hostingContext, services) => { // Fallback <HostFilteringOptions>(options => { if ( == null || == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = ["AllowedHosts"]?.Split(new[] { ';' }, ); // Fall back to "*" to disable. = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); // Change notification <IOptionsChangeTokenSource<HostFilteringOptions>>( new ConfigurationChangeTokenSource<HostFilteringOptions>()); <IStartupFilter, HostFilteringStartupFilter>(); }) .UseIIS() .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { = (); }); return builder; }
It can be seen that the CreateDefaultBuilder still uses the IConfigurationBuilder implementation, and the name of the default configuration file is written.
public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder(); if ((())) { (()); } if (args != null) { (new ConfigurationBuilder().AddCommandLine(args).Build()); } ((builderContext, options) => { (("Kestrel")); }) .ConfigureAppConfiguration((hostingContext, config) => { var env = ; ("", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{}.json", optional: true, reloadOnChange: true); if (()) { var appAssembly = (new AssemblyName()); if (appAssembly != null) { (appAssembly, optional: true); } } (); if (args != null) { (args); } }) .ConfigureLogging((hostingContext, logging) => { (("Logging")); (); (); (); }) .ConfigureServices((hostingContext, services) => { // Fallback <HostFilteringOptions>(options => { if ( == null || == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = ["AllowedHosts"]?.Split(new[] { ';' }, ); // Fall back to "*" to disable. = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); // Change notification <IOptionsChangeTokenSource<HostFilteringOptions>>( new ConfigurationChangeTokenSource<HostFilteringOptions>()); <IStartupFilter, HostFilteringStartupFilter>(); }) .UseIIS() .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { = (); }); return builder; }
Due to the above code, we can use it in the application root directory
andappsettings.{}.json
This form of default configuration file name
Moreover, since the Main method performs a Build method call to the configuration file by default
public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); }
We can use injection to obtain the default configuration file object IConfigurationRoot/IConfiguration, code snippet
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }
Why is this? Because when executing the Build method, the default configuration file object has been added to the ServiceCollection inside the method.
var services = new ServiceCollection(); (_options); <IHostingEnvironment>(_hostingEnvironment); <>(_hostingEnvironment); (_context); var builder = new ConfigurationBuilder() .SetBasePath(_hostingEnvironment.ContentRootPath) .AddConfiguration(_config); _configureAppConfigurationBuilder?.Invoke(_context, builder); var configuration = (); <IConfiguration>(configuration); _context.Configuration = configuration;
The above code is very familiar, because in the file, we may have used the ServiceCollection object to add custom objects of the business system to the service context to facilitate subsequent interface injection.
Use of AddJsonFile method
Usually, we will use the default configuration file for development, or use the file name method of appsettings.{}.json to distinguish the development/test/product environment, and load different configuration files according to the environment variables; but this brings another management problem, the configuration parameters of the product environment and the development environment.
It is different. If you use environment variables to control the loading of configuration files, it may lead to risks such as password leakage; indeed, you can create this file manually in the product environment, but in this way, the release process will become very cumbersome, and the file will be overwritten if there are any errors or omissions.
We recommend using AddJsonFile to load product environment configuration, the code is as follows
public Startup(IConfiguration configuration, IHostingEnvironment env) { Configuration = AddCustomizedJsonFile(env).Build(); } public ConfigurationBuilder AddCustomizedJsonFile(IHostingEnvironment env) { var build = new ConfigurationBuilder(); ().AddJsonFile("", true, true); if (()) { (("/data/sites/config", ""), true, true); } return build; }
Use the AddCustomizedJsonFile method to create a ConfigurationBuilder object and overwrite the system's default ConfigurationBuilder object. In the method, the configuration file of the development environment is loaded by default. In product mode, the directory /data/sites/config/ file is loaded.
Different from worrying about configuration file conflicts, the content of the same key value will be overwritten by the subsequently added configuration file.
Configuration file changes
When calling AddJsonFile, we see that there are 5 overloaded methods in this method
One of the methods contains 4 parameters, the code is as follows
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if ((path)) { throw new ArgumentException(Resources.Error_InvalidFilePath, nameof(path)); } return (s => { = provider; = path; = optional; = reloadOnChange; (); }); }
In this method, there is a parameter bool reloadOnChange. From the parameter description, it can be seen that this value indicates whether to reload when the file changes. The default value is: false; generally when manually loading the configuration file, that is, when calling the AddJsonFile method, it is recommended to set the parameter value to true.
Then .netcore is used to monitor file changes and when to reload, see the following code
public IConfigurationRoot Build() { var providers = new List<IConfigurationProvider>(); foreach (var source in Sources) { var provider = (this); (provider); } return new ConfigurationRoot(providers); }
When we execute the .Build method, the last line of code inside the method creates and returns a ConfigurationRoot object using the parameters of the AddJsonFile method.
In the ConfigurationRoot constructor
public ConfigurationRoot(IList<IConfigurationProvider> providers) { if (providers == null) { throw new ArgumentNullException(nameof(providers)); } _providers = providers; foreach (var p in providers) { (); (() => (), () => RaiseChanged()); } }
We see that the method reads the configuration files added through the AddJsonFile method at one time, and a listener ChangeToken is assigned to each configuration file separately, and the current file read object is bound.
Method into the listener
When a file changes, the listener will receive a notification and perform an atomic operation on the file at the same time.
private void RaiseChanged() { var previousToken = (ref _changeToken, new ConfigurationReloadToken()); (); }
Since the AddJsonFile method uses JsonConfigurationSource internally, the overloaded method of Build constructs a JsonConfigurationProvider to read the object and view the code
public override IConfigurationProvider Build(IConfigurationBuilder builder) { EnsureDefaults(builder); return new JsonConfigurationProvider(this); }
The JsonConfigurationProvider inherits from the FileConfigurationProvider class, which is located in the assemblyInside
The process of reloading the configuration file by the listener in the constructor of FileConfigurationProvider is implemented
public FileConfigurationProvider(FileConfigurationSource source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Source = source; if ( && != null) { ( () => (), () => { (); Load(reload: true); }); } }
It is worth noting that the listener does not reload the configuration file as soon as possible after receiving the file change notification. You can see inside the method that there is a()
, and the default value of ReloadDelay is: 250ms, the description of this property is
- Get or set the number of milliseconds to which reload will wait, and then call the "Load" method. This helps avoid triggering reloads before fully writing to the file. The default value is 250
- It is gratifying that we can customize the value, if the business is not particularly urgent for file changes, you can set the value to a large amount of time, which is not recommended to do.
Conclusion
The above is the internal execution process of configuration file loading. From this we realize how the default configuration file is loaded and how the default configuration file is injected into the system. We also learned the process of choosing to load a custom configuration file in different environments; but when the configuration file changes, how the system reloads the configuration file into memory.