hello,大家好,今天还是橙子老哥的分享时间,希望大家一起学习,一起进步。
欢迎加入.net意社区,第一时间了解我们的动态
官方地址:https://ccnetcore.com
微信公众号:搜索意.Net / 或添加橙子老哥微信:chegnzilaoge520
管道也走了,日志也走了,不得来手配置?本章就带代价玩一玩IConfiguration,我们如何自定义扩展自己的配置,以及配置的自动刷新是个什么回事?
1、自定义配置
当然,一如既往,IConfiguration如何使用,相信大家已了如指掌。
var config= app.Services.GetRequiredService<IConfiguration>();
var test=config.GetValue<string>("Test");
这里就不在过多赘述了,我们先看看它如何新增自己的配置源
- 创建自定义配置提供程序首先,创建一个继承自 IConfigurationProvider 的类。
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;public class CustomConfigurationSource : IConfigurationSource
{public IConfigurationProvider Build(IConfigurationBuilder builder){return new CustomConfigurationProvider();}
}public class CustomConfigurationProvider : ConfigurationProvider
{public override void Load(){// 这里可以加载你的自定义配置// 示例: 从数据库、文件、API 等Data = new Dictionary<string, string>{{"MyApp:Setting1", "Value1"},{"MyApp:Setting2", "Value2"}};}
}
- 扩展 IConfigurationBuilder
在自定义配置源后,你需要创建一个扩展方法,以方便地向 IConfigurationBuilder 添加你的配置源。
public static class CustomConfigurationExtensions
{public static IConfigurationBuilder AddCustomConfiguration(this IConfigurationBuilder builder){builder.Sources.Insert(0, new CustomConfigurationSource());return builder;}
}
是不是非常简单?这里我们要提供扩展的核心对象,IConfigurationSource
和IConfigurationProvider
通过IConfigurationSource
的Build方法去创建我们自定义的IConfigurationProvider
2、IConfiguration 原理
我们从入口开始查看,config.GetValue
他的GetValue方法
//扩展方法1,没做什么public static T? GetValue<T>(this IConfiguration configuration,string key){return configuration.GetValue<T>(key, default (T));}//扩展方法2,没做什么public static T? GetValue<T>(this IConfiguration configuration,string key,T defaultValue){return (T) configuration.GetValue(typeof (T), key, (object) defaultValue);}//扩展方法3,通过GetSection获取IConfigurationSection ,IConfigurationSection 再去获取到真正的值,最后通过ConfigurationBinder将值绑定转换public static object? GetValue(this IConfiguration configuration,Type type,string key,object? defaultValue){ThrowHelper.ThrowIfNull((object) configuration, nameof (configuration));ThrowHelper.ThrowIfNull((object) type, nameof (type));IConfigurationSection section = configuration.GetSection(key);string str = section.Value;return str != null ? ConfigurationBinder.ConvertValue(type, str, section.Path) : defaultValue;}
//扩展方法3,通过GetSection获取IConfigurationSection ,IConfigurationSection 再去获取到真正的值,最后通过ConfigurationBinder将值绑定转换
看到这里,大致就能猜到这套玩法的大概了,我们不能直接通过IConfiguration
去获取到配置的值,需要通过一个key获取到它的节点IConfigurationSection
,这个节点才能获取到对应的配置值
但是由于配置文件直接读取出来,肯定就是一个字符串,还需要通过ConfigurationBinder
将字符串转换成对应的我们需要的类型,就是这个思路。
上述的往里走的GetSection
是 IConfiguration
接口下的方法,它有三个实现:
- ConfigurationManager (依赖注入的实现,入口)
- ConfigurationRoot (通过Manager进行创建)
- ConfigurationSection (在ConfigurationRoot 上包一层)
我们按照这个顺序去看,最核心的就是这一句话
ConfigurationManager 类:public IConfigurationSection GetSection(string key){return (IConfigurationSection) new ConfigurationSection((IConfigurationRoot) this, key);}
通过Manager的GetSection转换成IConfigurationRoot,丢到IConfigurationSection包一层
ConfigurationManager 主要用于管理配置的操作,我们新增配置源全靠它
public interface IConfigurationBuilder{IDictionary<string, object> Properties { get; }IList<IConfigurationSource> Sources { get; }IConfigurationBuilder Add(IConfigurationSource source);IConfigurationRoot Build();}
ConfigurationRoot 主要用于配置的重载,以及管理配置的提供者
public interface IConfigurationRoot : IConfiguration{void Reload();IEnumerable<IConfigurationProvider> Providers { get; }}
ConfigurationSection 主要用于获取实际的配置值
public interface IConfigurationSection : IConfiguration{string Key { get; }string Path { get; }string? Value { get; set; }}
好家伙,绕来绕去,一个IConfiguration
操作的ConfigurationManager 、ConfigurationRoot 、ConfigurationSection 就是一层套一层,不得不说,这样设计,真的很是巧妙
这种设计,我们是不是可以用到自己业务场景中?像需要扩展数据源相关的业务,用这种设计是非常合适的,例如: 不同plc数据源统一的读写操作等等、对接第三方系统数据等,不打算来尝试下?
3、IConfigurationSource与IConfigurationProvider原理
我们上面把整个配置的核心对象转了一圈,清楚了如何使用的原理,但是具体我们怎么去从json的配置文件中获取呢?这里我们再走下去
我们获取具体的配置值,肯定是通过ConfigurationSection
对象的value
属性进行获取的
public string? Value{get => this._root[this.Path];set => this._root[this.Path] = value;}
这里又很巧妙,ConfigurationSection又调用了IConfigurationRoot中的索引方法,我们再到ConfigurationRoot中看看
public string? this[string key]{get => ConfigurationRoot.GetConfiguration(this._providers, key);set => ConfigurationRoot.SetConfiguration(this._providers, key, value);}//真正获取值的地方internal static string? GetConfiguration(IList<IConfigurationProvider> providers, string key){for (int index = providers.Count - 1; index >= 0; --index){string configuration;if (providers[index].TryGet(key, out configuration))return configuration;}return (string) null;}
这里,又是一个倒叙遍历的操作,但和管道模型意义不一样,它的目的是为了后续新增的配置源,会有限覆盖前面的配置源,所以从越后面的配置源开始找,找到了直接就return 啦
仔细看,这个providers,不就是我们之前自定义新增的吗?那我们是如何新增到providers集合里面的呢?
这里,我们看他通过ConfigurationBuilder
将providers塞进去的
public IConfigurationRoot Build(){List<IConfigurationProvider> providers = new List<IConfigurationProvider>();foreach (IConfigurationSource source in this._sources){IConfigurationProvider configurationProvider = source.Build((IConfigurationBuilder) this);providers.Add(configurationProvider);}return (IConfigurationRoot) new ConfigurationRoot((IList<IConfigurationProvider>) providers);}
而它的Provider又是通过_sources来的,我们还要再往上,看看_sources是怎么加进去的
public IDictionary<string, object> Properties { get; } = (IDictionary<string, object>) new Dictionary<string, object>();public IConfigurationBuilder Add(IConfigurationSource source){ThrowHelper.ThrowIfNull((object) source, nameof (source));this._sources.Add(source);return (IConfigurationBuilder) this;}
到这里,就很清楚了,我们是通过IConfigurationBuilder
去新增我们的IConfigurationSource
,然后通过Build()
之后,将sources中的Provider塞到root中
(IConfigurationRoot) new ConfigurationRoot((IList<IConfigurationProvider>) providers)
往后就形成了闭环,Sorce、Provider、IConfiguration
4、默认实现
结束了吗?还没有,我们前面讲了,如何新增自己的配置,多个配置之间如何进行获取到值,以及他们值是如何进行管理的,一样的,即使我们什么实现也不加,默认.netcore中也能json的配置文件,我们看看他是怎么实现的(猜也猜的到,读取file,转json)
JsonConfigurationProvider
是json的实现,它继承于FileConfigurationProvider
public class JsonConfigurationProvider : FileConfigurationProvider{/// <summary>Initializes a new instance with the specified source.</summary>/// <param name="source">The source settings.</param>public JsonConfigurationProvider(JsonConfigurationSource source): base((FileConfigurationSource) source){}/// <summary>Loads the JSON data from a stream.</summary>/// <param name="stream">The stream to read.</param>public override void Load(Stream stream){try{this.Data = JsonConfigurationFileParser.Parse(stream);}catch (JsonException ex){throw new FormatException(SR.Error_JSONParseError, (Exception) ex);}}}
嗯,对的,就这么几行,stream是通过FileConfigurationProvider
来的,通过json的一个静态类,转成key、value的键值对,给data就行了
5、隐藏知识-IOptionsMonitor实时读取问题
大家是否对 IOptionsMonitor
为什么能实时对配置文件进行一个监控?配置文件变了,IOptionsMonitor
的值也就变了?但是!有时候好像又不会生效,比如立马更改完之后去读取,好像又读取不到最新的值
这是有bug?我们一探究竟
本着中国那句古话,来都来了,不妨我们看看,FileConfigurationProvider
,它是怎么看监控到文件的?
我们看看它的构造函数:
public FileConfigurationProvider(FileConfigurationSource source){ThrowHelper.ThrowIfNull((object) source, nameof (source));this.Source = source;if (!this.Source.ReloadOnChange || this.Source.FileProvider == null)return;this._changeTokenRegistration = ChangeToken.OnChange((Func<IChangeToken>) (() => this.Source.FileProvider.Watch(this.Source.Path)), (Action) (() =>{Thread.Sleep(this.Source.ReloadDelay);this.Load(true);}));}
初始化的时候,使用了this.Source.FileProvider.Watch(this.Source.Path)
去监控文件的变化。这个是来自于
IFileProvider
这个接口的能力,但是这里我们也能注意到,在重新load配置文件的时候,执行了一个Thread.Sleep(this.Source.ReloadDelay);
这个属性,是在FileConfigurationSource中,我们可以进行更改
public int ReloadDelay { get; set; } = 250;
思考下,为什么会有一个250ms的限制?因为配置文件不是用来实时存数据的!!!就这么简单,好像是这个道理,如果有大量又实时更新的数据,为什么不丢数据库?你说是吧~