浅析 .Net Core中Json配置的自动更新
Pre
很早在看 Jesse 的Asp.net Core快速入门的课程的时候就了解到了在Asp .net core中,如果添加的Json配置被更改了,是支持自动重载配置的,作为一名有着严重"造轮子"情节的程序员,最近在折腾一个博客系统,也想造出一个这样能自动更新以Mysql为数据源的ConfigureSource,于是点开了AddJsonFile这个拓展函数的源码,发现别有洞天,蛮有意思,本篇文章就简单地聊一聊Json config的ReloadOnChange是如何实现的,在学习ReloadOnChange的过程中,我们会把Configuration也顺带撩一把?,希望对小伙伴们有所帮助.
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(option =>
{
option.AddJsonFile("appsettings.json",optional:true,reloadOnChange:true);
})
.UseStartup<Startup>();
在Asp .net core中如果配置了json数据源,把reloadOnChange属性设置为true即可实现当文件变更时自动更新配置,这篇博客我们首先从它的源码简单看一下,看完你可能还是会有点懵的,别慌,我会对这些代码进行精简,做个简单的小例子,希望能对你有所帮助.
一窥源码
AddJson
首先,我们当然是从这个我们耳熟能详的扩展函数开始,它经历的演变过程如下.
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder,string path,bool optional,bool reloadOnChange)
{
return builder.AddJsonFile((IFileProvider) null, path, optional, reloadOnChange);
}
传递一个null的FileProvider给另外一个重载Addjson函数.
敲黑板,Null的FileProvider很重要,后面要考?.
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder,IFileProvider provider,string path,bool optional,bool reloadOnChange)
{
return builder.AddJsonFile((Action<JsonConfigurationSource>) (s =>
{
s.FileProvider = provider;
s.Path = path;
s.Optional = optional;
s.ReloadOnChange = reloadOnChange;
s.ResolveFileProvider();
}));
}
把传入的参数演变成一个Action委托给JsonConfigurationSource
的属性赋值.
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)
{
return builder.Add<JsonConfigurationSource>(configureSource);
}
最终调用的builder.add(action)方法.
public static IConfigurationBuilder Add<TSource>(this IConfigurationBuilder builder,Action<TSource> configureSource)where TSource : IConfigurationSource, new()
{
TSource source = new TSource();
if (configureSource != null)
configureSource(source);
return builder.Add((IConfigurationSource) source);
}
在Add方法里,创建了一个Source实例,也就是JsonConfigurationSource实例,然后把这个实例传为刚刚的委托,这样一来,我们在最外面传入的"appsettings.json",optional:true,reloadOnChange:true
参数就作用到这个示例上了.
最终,这个实例添加到builder中.那么builder又是什么?它能干什么?
ConfigurationBuild
前面提及的builder默认情况下是ConfigurationBuilder
,我对它的进行了简化,关键代码如下.
public class ConfigurationBuilder : IConfigurationBuilder
{
public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();
public IConfigurationBuilder Add(IConfigurationSource source)
{
Sources.Add(source);
return this;
}
public IConfigurationRoot Build()
{
var providers = new List<IConfigurationProvider>();
foreach (var source in Sources)
{
var provider = source.Build(this);
providers.Add(provider);
}
return new ConfigurationRoot(providers);
}
}
可以看到,这个builder中有个集合类型的Sources,这个Sources可以保存任何实现了IConfigurationSource
的Source,前面聊到的JsonConfigurationSource
就是实现了这个接口,常用的还有MemoryConfigurationSource
,XmlConfigureSource
,CommandLineConfigurationSource
等.
另外,它有一个很重要的build方法,这个build方法在WebHostBuilder
方法执行build
的时候也被调用,不要问我WebHostBuilder.builder
方法什么执行的?.
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
在ConfigureBuilder的方法里面就调用了每个Source的Builder方法,我们刚刚传入的是一个JsonConfigurationSource
,所以我们有必要看看JsonSource的builder做了什么.
这里是不是被这些builder绕哭了? 别慌,下一篇文章中我会讲解如何自定义一个ConfigureSoure,会把Congigure系列类UML类图整理一下,应该会清晰很多.
JsonConfigurationSource
public class JsonConfigurationSource : FileConfigurationSource
{
public override IConfigurationProvider Build(IConfigurationBuilder builder)
{
EnsureDefaults(builder);
return new JsonConfigurationProvider(this);
}
}
这就是JsonConfigurationSource
的所有代码,未精简,它只实现了一个Build方法,在Build内,EnsureDefaults被调用,可别小看它,之前那个空的FileProvider在这里被赋值了.
public void EnsureDefaults(IConfigurationBuilder builder)
{
FileProvider = FileProvider ?? builder.GetFileProvider();
}
public static IFileProvider GetFileProvider(this IConfigurationBuilder builder)
{
return new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty);
}
可以看到这个FileProvider默认情况下就是PhysicalFileProvider
,为什么对这个FileProvider
如此宠幸让我花如此大的伏笔要强调它呢?往下看.
JsonConfigurationProvider && FileConfigurationProvider
在JsonConfigurationSource的build方法内,返回的是一个JsonConfigurationProvider实例,所以直觉告诉我,在它的构造函数内必有猫腻?.
public class JsonConfigurationProvider : FileConfigurationProvider
{
public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }
public override void Load(Stream stream)
{
try {
Data = JsonConfigurationFileParser.Parse(stream);
} catch (JsonReaderException e)
{
throw new FormatException(Resources.Error_JSONParseError, e);
}
}
}
看不出什么的代码,事出反常必有妖~~
看看base的构造函数.
public FileConfigurationProvider(FileConfigurationSource source)
{
Source = source;
if (Source.ReloadOnChange && Source.FileProvider != null)
{
_changeTokenRegistration = ChangeToken.OnChange(
() => Source.FileProvider.Watch(Source.Path),
() => {
Thread.Sleep(Source.ReloadDelay);
Load(reload: true);
});
}
}
真是个天才,问题就在这个构造函数里,它构造函数调用了一个ChangeToken.OnChange
方法,这是实现ReloadOnChange的关键,如果你点到这里还没有关掉,恭喜,好戏开始了.
ReloadOnChange
Talk is cheap. Show me the code (屁话少说,放码
过来).
public static class ChangeToken
{
public static ChangeTokenRegistration<Action> OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
{
return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer);
}
}
OnChange方法里,先不管什么func,action,就看看这两个参数的名称,producer,consumer,生产者,消费者,不知道看到这个关键词想到的是什么,反正我想到的是小学时学习食物链时的?与?.
那么我们来看看这里的?是什么,?又是什么,还得回到FileConfigurationProvider
的构造函数.
可以看到生产者?是:
() => Source.FileProvider.Watch(Source.Path)
消费者?是:
() => {
Thread.Sleep(Source.ReloadDelay);
Load(reload: true);
}
我们想一下,一旦有一条?跑出来,就立马被?吃了,
那我们这里也一样,一旦有FileProvider.Watch返回了什么东西,就会发生Load()事件来重新加载数据.
?与?好理解,可是代码就没那么好理解了,我们通过OnChange
的第一个参数Func<IChangeToken> changeTokenProducer
方法知道,这里的?,其实是IChangeToken
.
IChangeToken
public interface IChangeToken
{
bool HasChanged { get; }
bool ActiveChangeCallbacks { get; }
IDisposable RegisterChangeCallback(Action<object> callback, object state);
}
IChangeToken的重点在于里面有个RegisterChangeCallback方法,?吃?的这件事,就发生在这回调方法里面.
我们来做个?吃?的实验.
实验1
static void Main()
{
//定义一个C:\Users\liuzh\MyBox\TestSpace目录的FileProvider
var phyFileProvider = new PhysicalFileProvider("C:\\Users\\liuzh\\MyBox\\TestSpace");
//让这个Provider开始监听这个目录下的所有文件
var changeToken = phyFileProvider.Watch("*.*");
//注册?吃?这件事到回调函数
changeToken.RegisterChangeCallback(_=> { Console.WriteLine("老鼠被蛇吃"); }, new object());
//添加一个文件到目录
AddFileToPath();
Console.ReadKey();
}
static void AddFileToPath()
{
Console.WriteLine("老鼠出洞了");
File.Create("C:\\Users\\liuzh\\MyBox\\TestSpace\\老鼠出洞了.txt").Dispose();
}
这是运行结果
可以看到,一旦在监听的目录下创建文件,立即触发了执行回调函数,但是如果我们继续手动地更改(复制)监听目录中的文件,回调函数就不再执行了.
这是因为changeToken监听到文件变更并触发回调函数后,这个changeToken的使命也就完成了,要想保持一直监听,那么我们就在在回调函数中重新获取token,并给新的token的回调函数注册通用的事件,这样就能保持一直监听下去了.
这也就是ChangeToken.Onchange所作的事情,我们看一下源码.
public static class ChangeToken
{
public static ChangeTokenRegistration<Action> OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
{
return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer);
}
}
public class ChangeTokenRegistration<TAction>
{
private readonly Func<IChangeToken> _changeTokenProducer;
private readonly Action<TAction> _changeTokenConsumer;
private readonly TAction _state;
public ChangeTokenRegistration(Func<IChangeToken> changeTokenProducer, Action<TAction> changeTokenConsumer, TAction state)
{
_changeTokenProducer = changeTokenProducer;
_changeTokenConsumer = changeTokenConsumer;
_state = state;
var token = changeTokenProducer();
RegisterChangeTokenCallback(token);
}
private void RegisterChangeTokenCallback(IChangeToken token)
{
token.RegisterChangeCallback(_ => OnChangeTokenFired(), this);
}
private void OnChangeTokenFired()
{
var token = _changeTokenProducer();
try
{
_changeTokenConsumer(_state);
}
finally
{
// We always want to ensure the callback is registered
RegisterChangeTokenCallback(token);
}
}
}
简单来说,就是给token注册了一个OnChangeTokenFired
的回调函数,仔细看看OnChangeTokenFired
里做了什么,总体来说三步.
获取一个新的token.
调用消费者进行消费.
给新获取的token再次注册一个
OnChangeTokenFired
的回调函数.
如此周而复始~~
实验2
既然知道了OnChange的工作方式,那么我们把实验1的代码修改一下.
static void Main()
{
var phyFileProvider = new PhysicalFileProvider("C:\\Users\\liuzh\\MyBox\\TestSpace");
ChangeToken.OnChange(() => phyFileProvider.Watch("*.*"),
() => { Console.WriteLine("老鼠被蛇吃"); });
Console.ReadKey();
}
执行效果看一下
可以看到,只要被监控的目录发生了文件变化,不管是新建文件,还是修改了文件内的内容,都会触发回调函数,其实JsonConfig中,这个回调函数就是Load(),它负责重新加载数据,可也就是为什么Asp .net core中如果把ReloadOnchang设置为true后,Json的配置一旦更新,配置就会自动重载.
PhysicalFilesWatcher
那么,为什么文件一旦变化,就会触发ChangeToken的回调函数呢? 其实PhysicalFileProvider
中调用了PhysicalFilesWatcher
对文件系统进行监视,观察PhysicalFilesWatcher的构造函数,可以看到PhysicalFilesWatcher
需要传入FileSystemWatcher
,FileSystemWatcher
是system.io
下的底层IO类,在构造函数中给这个Watcher的Created,Changed,Renamed,Deleted注册EventHandler事件,最终,在这些EventHandler中会调用ChangToken的回调函数,所以文件系统一旦发生变更就会触发回调函数.
public PhysicalFilesWatcher(string root,FileSystemWatcher fileSystemWatcher,bool pollForChanges,ExclusionFilters filters)
{
this._root = root;
this._fileWatcher = fileSystemWatcher;
this._fileWatcher.IncludeSubdirectories = true;
this._fileWatcher.Created += new FileSystemEventHandler(this.OnChanged);
this._fileWatcher.Changed += new FileSystemEventHandler(this.OnChanged);
this._fileWatcher.Renamed += new RenamedEventHandler(this.OnRenamed);
this._fileWatcher.Deleted += new FileSystemEventHandler(this.OnChanged);
this._fileWatcher.Error += new ErrorEventHandler(this.OnError);
this.PollForChanges = pollForChanges;
this._filters = filters;
this.PollingChangeTokens = new ConcurrentDictionary<IPollingChangeToken, IPollingChangeToken>();
this._timerFactory = (Func<Timer>) (() => NonCapturingTimer.Create(new TimerCallback(PhysicalFilesWatcher.RaiseChangeEvents), (object) this.PollingChangeTokens, TimeSpan.Zero, PhysicalFilesWatcher.DefaultPollingInterval));
}
蒋金楠老师有一篇优秀的文章介绍FileProvider
,有兴趣的可以看一下
https://www.cnblogs.com/artech/p/net-core-file-provider-02.html.
如果你和我一样,对源码感兴趣,可以从官方的aspnet/Extensions
中下载源码研究:https://github.com/aspnet/Extensions
在下一篇文章中,我会讲解如何自定义一个以Mysql为数据源的ConfigureSoure,并实现自动更新功能,同时还会整理Configure相关类的UML类图,有兴趣的可以关注我以便第一时间收到下篇文章.
本文章涉及的代码地址:https://github.com/liuzhenyulive/MiniConfiguration
原文地址:https://www.cnblogs.com/CoderAyu/p/10776845.html
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
浅析 .Net Core中Json配置的自动更新相关推荐
- VBA小白的福音 如何在EXCEL中实现连续编号自动更新打印?
VBA小白的福音 如何在EXCEL中实现连续编号自动更新打印? 用下列方法,无论你需要输入连续编号的单元格是否改变,均不需要改变代码. 第一步:将下列代码拷贝到宏中(方法请问度娘,搜代码的人应该会的) ...
- .Net Core配置与自动更新
.Net Core 将之前Web.Config中的配置迁移到了appsettings.json文件中,并使用ConfigurationBuilder来读取这个配置文件.并可设置在配置文件变化以后,自动 ...
- 在 Ubuntu 14.04 LTS 中配置 certbot 自动更新
目录 前言 一.安装 snapd 二.确保安装的 snapd 版本为最新版本 三.删除 certbot-auto 及系统中已安装的任何 certbot 软件包 四.安装 Certbot 五.建立系统的 ...
- ASP.NET Core中的配置
配置 参考文件点击跳转 配置来源 命令行参数 自定义提供程序 目录文件 环境变量 内存中的.NET 对象 文件 默认配置 CreateDefaultBuilder方法提供有默认配置,在这个方法中会接收 ...
- 实现.Net程序中OpenTracing采样和上报配置的自动更新
前言 OpenTracing是一个链路跟踪的开放协议,已经有开源的.net实现:opentracing-csharp,同时支持.net framework和.net core,Github地址:htt ...
- EF Core中高效批量删除、更新数据的Zack.EFCore.Batch发布三个新特性
Zack.EFCore.Batch是一个支持在Entity Framework Core中高效删除和更新数据的开源库.我们知道,EF Core中不支持高效的删除和更新数据,所有的更新和操作都是逐条数据 ...
- 通过C#中的在线文件自动更新应用程序
目录 介绍 背景 使用代码 兴趣点 本文介绍如何在不使用ClickOnce的情况下为您的应用程序提供一键更新或自动更新功能. 下载演示项目-586.48 KB 下载源-1.13 MB 下载Readme ...
- win10中关闭应用程序自动更新
目录 ■设置 1.windows设置 ⇒ 更新和安全 2.激活 ⇒ 转到 Microsoft Store 3.... ⇒ 设置 4.自动更新应用 ⇒ 关闭 ■更新的下载与安装 ・下载 ・安装 ■当前的 ...
- wp配置后台自动更新
2019独角兽企业重金招聘Python工程师标准>>> Update Configuration Automatic updates can be configured using ...
最新文章
- 通关制单机器人_2020关务节|“数字供应链与智能通关”论坛——如何打造云上跨境贸易生态圈...
- 面试必备:缓存穿透,缓存雪崩的四种解决方案
- 微信小程序界面跳转(1)
- aspx页面中使用%= %的注意事项
- 互联网1分钟 | 0328 阿里巴巴收购企业协作软件Teambition;完美世界:与谷歌达成战略合作,积极探索VR等新游戏类型...
- LeetCode 81 搜索旋转排序数组 II
- 编译OpenJDK:invalid configuration Files: machine Files not recognized
- 简单的mock-server 解决方案
- ad转3d视图快捷键_AD工具快捷键
- 基于Tableau探索分析世界银行提供的关于科学技术的数据
- android 信号检测,卫星、手机信号都能测!安卓神器你值得拥有
- 几何校正(坐标变换+灰度重采样)
- bilibili、腾讯视频下载方法及过程中遇到的一些问题
- 云服务器下行_阿里云ECS服务器下行带宽和上行带宽详解及选择
- mac 命令修改dns服务器,MAC设置DNS的步骤
- 解决safari里面淘宝京东页面无法打开以及打开后乱码的问题!
- Nodejs 使用 Buffer 将图片转为 base64
- 海康2017校招C++开发岗位笔试题
- 从Django的SECTET_KEY到代码执行
- 高德地图添加瓦片图层