Recently, I have studied the source code implementation of .NetCore configuration options and learned a lot of things. This article first writes about the learning results of IConfiguration, and add it later on Options
Core category
- ConfigurationBuilder:IConfigurationBuilder (Build IConfiguration)
- IConfigurationSource (Configuration data source)
- IConfigurationProvider (Convert the original structure of the configuration source to IDictionary<string, string>)
- ConfigurationRoot:IConfigurationRoot:IConfiguration (Configure the root node)
Build
ConfigurationBuilder
Below is the main code in ConfigurationBuilder
You can see that the main function of ConfigurationBuilder is to configure the data source into the collection.
When Build, call the Build function of IConfigurationSource in turn, and add the returned IConfigurationProvider to the List
Finally, use the IConfigurationProvider collection to build a ConfigurationRoot object
public IList<IConfigurationSource> Sources = new List<IConfigurationSource>(); public IConfigurationBuilder Add(IConfigurationSource source) { (source); return this; } public IConfigurationRoot Build() { List<IConfigurationProvider> list = new List<IConfigurationProvider>(); foreach (IConfigurationSource source in Sources) { IConfigurationProvider item = (this); (item); } return new ConfigurationRoot(list); }
IConfigurationSource
public class EnvironmentVariablesConfigurationSource : IConfigurationSource { public string Prefix; public IConfigurationProvider Build(IConfigurationBuilder builder) { return new EnvironmentVariablesConfigurationProvider(Prefix); } public EnvironmentVariablesConfigurationSource() { } } public class CommandLineConfigurationSource : IConfigurationSource { public IDictionary<string, string> SwitchMappings; public IEnumerable<string> Args; public IConfigurationProvider Build(IConfigurationBuilder builder) { return new CommandLineConfigurationProvider(Args, SwitchMappings); } public CommandLineConfigurationSource() { } } //JsonConfigurationSource inherits from FileConfigurationSource, I'll combine it into one herepublic abstract class JsonConfigurationSource : IConfigurationSource { public IFileProvider FileProvider { get; set; } public string Path { get; set; } public bool Optional { get; set; } public bool ReloadOnChange { get; set; } public int ReloadDelay { get; set; } = 250; public Action<FileLoadExceptionContext> OnLoadException { get; set; } public IConfigurationProvider Build(IConfigurationBuilder builder) { FileProvider = FileProvider ?? (); OnLoadException = OnLoadException ?? (); return new JsonConfigurationProvider(this); } public void ResolveFileProvider() { if (FileProvider == null && !(Path) && (Path)) { string directoryName = (Path); string text = (Path); while (!(directoryName) && !(directoryName)) { text = ((directoryName), text); directoryName = (directoryName); } if ((directoryName)) { FileProvider = new PhysicalFileProvider(directoryName); Path = text; } } } }
The above shows three commonly used ConfigurationSources, all of which are relatively simple.
It is also easy to see that the function of ConfigurationSource is to configure the data source and not parse the data.
The function of parsing data sources is completed by IConfigurationProvider
ConfigurationProvider
The following are 5 functions defined by the IConfigurationProvider interface
public interface IConfigurationProvider { bool TryGet(string key, out string value); void Set(string key, string value); IChangeToken GetReloadToken(); void Load(); IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath); }
ConfigurationProvider is an abstract class that inherits the IConfigurationProvider interface
When creating a new provider, you usually choose to directly inherit the ConfigurationProvider. Next, let’s take a look at several core methods of ConfigurationProvider.
public abstract class ConfigurationProvider : IConfigurationProvider { private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken(); protected IDictionary<string, string> Data= new Dictionary<string, string>(); public virtual bool TryGet(string key, out string value)=>(key, out value); public virtual void Set(string key, string value)=>Data[key] = value; public virtual void Load(){} public IChangeToken GetReloadToken() { return _reloadToken; } protected void OnReload() { ConfigurationReloadToken configurationReloadToken = (ref _reloadToken, new ConfigurationReloadToken()); (); }
It can be inferred that:
- The Load function is responsible for reading data from source data and assigning values to the dictionary Data.
- ConfigurationProvider stores data in dictionary Data, adding and modifying is an operation on dictionary
- Each ConfigurationProvider will generate an IChangeToken, which generates a new token when the OnReload function is called, and the OnReload function of the original token is called.
ConfigurationRoot
In the Build function of ConfigurationBuilder, we generate a ConfigurationRoot and pass it all the ConfigurationProvider lists. Let's see what he did with our Provider.
private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken(); public ConfigurationRoot(IList<IConfigurationProvider> providers) { _providers = providers; _changeTokenRegistrations = new List<IDisposable>(); foreach (IConfigurationProvider p in providers) { (); (, delegate{ var oldToken=(ref _changeToken, new ConfigurationReloadToken()); (); }) } } public IChangeToken GetReloadToken()=>_changeToken;
The above code also simplifies some places. You can see that ConfigurationRoot mainly does two things when it is generated.
- 1. Call the Provider's Load function, which will assign a value to the Provider's Data
- 2. Read the ReloadToken of Provider. Each Reload event of Provider will trigger the Reload event of ConfigurationRoot's own ReloadToken.
The data source construction of the configured so far is analyzed!
Query
There are two basic methods for regular configuration query: indexer and GetSection(string key)
The rest of GetValue and so on are some extension methods, and this article will not study this.
Indexer
The query execution of the indexer is to flashback all Providers, then call the Provider's TryGet function, and the key is duplicated when querying, and the last added will take effect.
Assignment is to call the Set function of each Provider in sequence
public string this[string key] { get { for (int num = _providers.Count - 1; num >= 0; num--) { if (_providers[num].TryGet(key, out var value)) { return value; } } return null; } set { foreach (IConfigurationProvider provider in _providers) { (key, value); } } }
GetSection
public IConfigurationSection GetSection(string key) { return new ConfigurationSection(this, key); } public class ConfigurationSection : IConfigurationSection, IConfiguration { private readonly IConfigurationRoot _root; private readonly string _path; private string _key; public string Value { get { return _root[Path]; } set { _root[Path] = value; } } // = (":",paramList); public string this[string key] { get { return _root[(Path, key)]; } set { _root[(Path, key)] = value; } } public ConfigurationSection(IConfigurationRoot root, string path) { _root = root; _path = path; } public IConfigurationSection GetSection(string key) { return _root.GetSection((Path, key)); } public IEnumerable<IConfigurationSection> GetChildren() { return _root.GetChildrenImplementation(Path); } public IChangeToken GetReloadToken() { return _root.GetReloadToken(); } }
You can see that GetSection will generate a ConfigurationSection object
When the ConfigurationSection reads/sets the value, it actually uses: splicing to the query key, and then calls the assignment or query function of IConfigurationRoot (_root)
The above is probably the knowledge points about configuration and reading of Configuration. There is also a more in-depth link to object binding. Get<> Bind<> GetChildren(), etc., which are difficult to read. You need to read one line by one. You may study it later if you have time.
Finally, a small example of loading the configuration source from the data and dynamically updating it
DBConfiguration example
public void Run() { var builder = new ConfigurationBuilder(); var dataProvider = new DBDataProvider(); (new DBConfigurationSource() { DataProvider = dataProvider, ReloadOnChange = true, Table = "config" }); IConfigurationRoot config = (); (config["time"]); (() => { while (true) { (2000); ("config"); ($"Read configuration time:{config["time"]}"); } }); (20000); } public class DBConfigurationProvider : ConfigurationProvider { private DBConfigurationSource Source { get; } public DBConfigurationProvider(DBConfigurationSource source) { Source = source; } public override void Load() { if () { (() => (), LoadData); } LoadData(); } private void LoadData() { var data = (); Load(data); OnReload(); } public void Load(Dictionary<string, object> data) { var dic = new SortedDictionary<string, string>(); foreach (var element in data) { (, ?.ToString()); } = dic; } } public class DBConfigurationSource : IConfigurationSource { public DBDataProvider DataProvider { get; set; } public string Table { get; set; } public bool ReloadOnChange { get; set; } public bool Optional { get; set; } public DBConfigurationSource() { } public IConfigurationProvider Build(IConfigurationBuilder builder) { return new DBConfigurationProvider(this); } } public class DBDataProvider { private ConcurrentDictionary<string, CancellationTokenSource> tableToken = new ConcurrentDictionary<string, CancellationTokenSource>(); public DBDataProvider() { } public Dictionary<string, object> GetData(string table) { switch (table) { case "config": return GetConfig(); } return new Dictionary<string, object>(); } public void Update(string table) { ($"Update database datatable:{table}"); if ((table, out CancellationTokenSource cts)) { var oldCts = cts; tableToken[table] = new CancellationTokenSource(); (); } } private Dictionary<string, object> GetConfig() { var valueDic = new Dictionary<string, object>(); ("time", ()); ("weather", "windy"); ("people_number:male", 100); ("people_number:female", 150); return valueDic; } public IChangeToken Watch(string table) { var cts = (table, x => new CancellationTokenSource()); return new CancellationChangeToken(); } }
This is the end of this article about the specific implementation of .Net Core Configuration. For more related .Net Core Configuration content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!