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的限制?因为配置文件不是用来实时存数据的!!!就这么简单,好像是这个道理,如果有大量又实时更新的数据,为什么不丢数据库?你说是吧~