浅析 .Net Core中Json配置的自动更新

Pre

很早在看 Jesse 的Asp.net Core快速入门的课程的时候就了解到了在Asp .net core中,如果添加的Json配置被更改了,是支持自动重载配置的,作为一名有着严重"造轮子"情节的程序员,最近在折腾一个博客系统,也想造出一个这样能自动更新以Mysql为数据源的ConfigureSource,于是点开了AddJsonFile这个拓展函数的源码,发现别有洞天,蛮有意思,本篇文章就简单地聊一聊Json config的ReloadOnChange是如何实现的,在学习ReloadOnChange的过程中,我们会把Configuration也顺带撩一把?,希望对小伙伴们有所帮助.

Copy
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

首先,我们当然是从这个我们耳熟能详的扩展函数开始,它经历的演变过程如下.

Copy
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很重要,后面要考?.

Copy
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的属性赋值.

Copy
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)
{
return builder.Add<JsonConfigurationSource>(configureSource);
}

最终调用的builder.add(action)方法.

Copy
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,我对它的进行了简化,关键代码如下.

Copy
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方法什么执行的?.

Copy
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

在ConfigureBuilder的方法里面就调用了每个Source的Builder方法,我们刚刚传入的是一个JsonConfigurationSource,所以我们有必要看看JsonSource的builder做了什么.
这里是不是被这些builder绕哭了? 别慌,下一篇文章中我会讲解如何自定义一个ConfigureSoure,会把Congigure系列类UML类图整理一下,应该会清晰很多.

JsonConfigurationSource

Copy
public class JsonConfigurationSource : FileConfigurationSource
{
public override IConfigurationProvider Build(IConfigurationBuilder builder)
{
EnsureDefaults(builder);
return new JsonConfigurationProvider(this);
}
}

这就是JsonConfigurationSource的所有代码,未精简,它只实现了一个Build方法,在Build内,EnsureDefaults被调用,可别小看它,之前那个空的FileProvider在这里被赋值了.

Copy
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实例,所以直觉告诉我,在它的构造函数内必有猫腻?.

Copy
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的构造函数.

Copy
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 (屁话少说,放过来).

Copy
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的构造函数.

可以看到生产者?是:

Copy
() => Source.FileProvider.Watch(Source.Path)

消费者?是:

Copy

() => {
Thread.Sleep(Source.ReloadDelay);
Load(reload: true);
}

我们想一下,一旦有一条?跑出来,就立马被?吃了,

那我们这里也一样,一旦有FileProvider.Watch返回了什么东西,就会发生Load()事件来重新加载数据.

?与?好理解,可是代码就没那么好理解了,我们通过OnChange的第一个参数Func<IChangeToken> changeTokenProducer方法知道,这里的?,其实是IChangeToken.

IChangeToken

Copy
public interface IChangeToken
{
bool HasChanged { get; }

bool ActiveChangeCallbacks { get; }

IDisposable RegisterChangeCallback(Action<object> callback, object state);
}

IChangeToken的重点在于里面有个RegisterChangeCallback方法,?吃?的这件事,就发生在这回调方法里面.
我们来做个?吃?的实验.

实验1

Copy
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();
}

这是运行结果
640?wx_fmt=gif
可以看到,一旦在监听的目录下创建文件,立即触发了执行回调函数,但是如果我们继续手动地更改(复制)监听目录中的文件,回调函数就不再执行了.

这是因为changeToken监听到文件变更并触发回调函数后,这个changeToken的使命也就完成了,要想保持一直监听,那么我们就在在回调函数中重新获取token,并给新的token的回调函数注册通用的事件,这样就能保持一直监听下去了.
这也就是ChangeToken.Onchange所作的事情,我们看一下源码.

Copy
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里做了什么,总体来说三步.

  1. 获取一个新的token.

  2. 调用消费者进行消费.

  3. 给新获取的token再次注册一个OnChangeTokenFired的回调函数.

如此周而复始~~

实验2

既然知道了OnChange的工作方式,那么我们把实验1的代码修改一下.

Copy
static void Main()
{
var phyFileProvider = new PhysicalFileProvider("C:\\Users\\liuzh\\MyBox\\TestSpace");
ChangeToken.OnChange(() => phyFileProvider.Watch("*.*"),
() => { Console.WriteLine("老鼠被蛇吃"); });
Console.ReadKey();
}

执行效果看一下

640?wx_fmt=gif

可以看到,只要被监控的目录发生了文件变化,不管是新建文件,还是修改了文件内的内容,都会触发回调函数,其实JsonConfig中,这个回调函数就是Load(),它负责重新加载数据,可也就是为什么Asp .net core中如果把ReloadOnchang设置为true后,Json的配置一旦更新,配置就会自动重载.

PhysicalFilesWatcher

那么,为什么文件一旦变化,就会触发ChangeToken的回调函数呢? 其实PhysicalFileProvider中调用了PhysicalFilesWatcher对文件系统进行监视,观察PhysicalFilesWatcher的构造函数,可以看到PhysicalFilesWatcher需要传入FileSystemWatcher,FileSystemWatchersystem.io下的底层IO类,在构造函数中给这个Watcher的Created,Changed,Renamed,Deleted注册EventHandler事件,最终,在这些EventHandler中会调用ChangToken的回调函数,所以文件系统一旦发生变更就会触发回调函数.

Copy
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 
640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316115.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

E. Don‘t Really Like How The Story Ends(代码未补)

Don’t Really Like How The Story Ends 题意&#xff1a; 有n个点&#xff0c;m个边&#xff0c;现在要从1号边开始求dfs序&#xff0c;问最少加多少边可以是的dfs序是从1到n&#xff1f; 题解&#xff1a; dfs序的过程中&#xff0c;不走到叶子节点我们是无法回溯的&…

.NET Core 迁移躺坑记续集--Win下莫名其妙的超时

继上一集.NET Core 迁移躺坑记里说到遇到的各种问题并且弄了n个解决方案之后&#xff0c;特别是对于问题4的解决方案对于切换了HttpClientFactory我用了你家netcore 2.1下专门解决之前HttpClient口病已久的灵丹妙药了&#xff0c;信心满满的上线…..然后挂了&#xff0c;该超时…

使用Entity Framework Core访问数据库(Oracle篇)

前言哇。。看看时间 真的很久很久没写博客了 将近一年了。最近一直在忙各种家中事务和公司的新框架 终于抽出时间来更新一波了。本篇主要讲一下关于Entity Framework Core访问oracle数据库的采坑。。强调一下&#xff0c;本篇文章发布之前 关于Entity Framework Core访问oracl…

Asp.Net Core Docker镜像更新系统从wheezy改为stretch

之前写过一个在Asp.Net Core里调用System.Drawing.Common绘图的DEMO&#xff0c;部署到Docker里运行&#xff0c;需要更新Asp.Net Core镜像的操作系统。https://www.cnblogs.com/sunnytrudeau/p/9384620.html当时用的阿里云的源RUN echo "deb http://mirrors.aliyun.com/d…

Monster Hunter(2020南京M)

Monster Hunter(2020南京M) 题意&#xff1a; 给你一颗树&#xff0c;树上每个节点都是一个hpi 血量的怪物。打败每个怪物所需要的能量值为hpi 所 有 存 活 的 直 接 子 节 点 的 hpj 。每次必须要消灭父节点后才能消灭子节点。此外你还有m个魔咒&#xff0c;每个魔咒可以不…

网络数据采集(AngleSharp)-使用AngleSharp做html解析

有这么一本Python的书: <<Python 网络数据采集>>我准备用.NET Core及第三方库实现里面所有的例子. 这是第一部分, 主要使用的是AngleSharp: https://anglesharp.github.io/(文章的章节书与该书是对应的)发送Http请求在python里面这样发送http请求, 它使用的是pytho…

ASP.NET Core在Azure Kubernetes Service中的部署和管理

目标部署&#xff1a;掌握将aspnetcore程序成功发布到Azure Kubernetes Service&#xff08;AKS&#xff09;上管理&#xff1a;掌握将AKS上的aspnetcore程序扩容、更新版本准备工作注册 Azure 账户官网免费帐户Azure 免费帐户仅适用于新用户&#xff0c;并且仅限每个客户一个免…

深入研究 Mini ASP.NET Core,看看 ASP.NET Core 内部到底是如何运行的

几年前&#xff0c;Artech 老师写过一个 Mini MVC&#xff0c;用简单的代码告诉读者 ASP.NET MVC 内部到底是如何运行的。当时我研究完以后&#xff0c;受益匪浅&#xff0c;内心充满了对 Artech 老师的感激&#xff0c;然后用我自己理解的 MVC 知识&#xff0c;写了一篇 深入研…

一文读懂Asp.net core 依赖注入(Dependency injection)

一、什么是依赖注入首先在Asp.net core中是支持依赖注入软件设计模式&#xff0c;或者说依赖注入是asp.net core的核心&#xff1b;依赖注入&#xff08;DI&#xff09;和控制反转&#xff08;IOC&#xff09;基本是一个意思&#xff0c;因为说起来谁都离不开谁&#xff1b;或者…

P4619 [SDOI2018]旧试题

P4619 [SDOI2018]旧试题 题意&#xff1a; 求个式子&#xff1a; (∑i1A∑j1B∑k1Cd(i∗j∗k))mod(1097)(\sum_{i1}^{A}\sum_{j1}^{B}\sum_{k1}^{C}d(i*j*k))mod(10^97)(i1∑A​j1∑B​k1∑C​d(i∗j∗k))mod(1097) 题解&#xff1a; 原创博文1k纪念 很明显&#xff0c;莫比…

C#中使用Bogus创建模拟数据

原文&#xff1a;CREATING SAMPLE DATA FOR C#[1] 作者&#xff1a;Bruno Sonnino 译文&#xff1a;C#中使用Bogus创建模拟数据 译者&#xff1a; Lamond Lu背景在我每次写技术类博文的时候&#xff0c;经常做的一件事就是创建模拟数据。在每篇博文中&#xff0c;为了解释某些概…

CF1253E Antenna Coverage

CF1253E Antenna Coverage 题意&#xff1a; 现在有n个点&#xff0c;每个点的坐标为xi&#xff0c;以及一个范围值si&#xff0c;可以覆盖左右范围[xi-si,xisi] 每次操作&#xff0c;可以花费代价1让第i个天线的si增加1&#xff0c;每个天线都可以进行多次操作。现在请问你最…

使用BeetleX的TcpBenchmark工具进行百万设备模拟测试

其实TCP测试的工具有很多&#xff0c;那BeetleX工具所提供的特点又是什么呢&#xff1f;如果你需数十万的请求或模拟上百万的设备连接&#xff0c;那这个工具相信可以满足你的需要&#xff01;工具是基于BeetleX的基础功能扩展&#xff0c;支持多IP绑定可以轻松实现上百万的cli…

.net core Entity Framework 与 EF Core

重点讲 Entity Framework Core &#xff01;&#xff08;一&#xff09;Entity Framework它是适用于.NET 的对象关系映射程序 (ORM)&#xff0c;现在的EF6已经是久经沙场&#xff0c;并经历重重磨难&#xff0c;获得一致认可的数据访问技术&#xff08;原来加 Title 也挺有意思…

CF1253F Cheap Robot

CF1253F Cheap Robot 题意&#xff1a; 给你一张 N 个点的带权无向连通图&#xff0c;其中结点 1,2,…,k 为充电中心。 一个机器人在图中行走&#xff0c;假设机器人的电池容量为 c&#xff0c;则任何时刻&#xff0c;机器人的电量 x 都必须满足 c0≤x≤c。如果机器人沿着一…

asp.net core 3.0 gRPC框架小试

什么是gRPCgRPC是google开源的一个高性能、跨语言的RPC框架&#xff0c;基于HTTP2协议&#xff0c;采用ProtoBuf 定义的IDL。gRPC 的主要优点是&#xff1a;现代高性能轻量级 RPC 框架。协定优先 API 开发&#xff0c;默认使用协议缓冲区&#xff0c;允许与语言无关的实现。可用…

P1131 [ZJOI2007] 时态同步

P1131 [ZJOI2007] 时态同步 题意&#xff1a; 有一颗树&#xff0c;有一个点是激发器&#xff0c;从这个点开始可以产生一个激励电流&#xff0c;通过导线传向每一个它所连接的节点&#xff0c;经过一个边的花费为w[i],你有一个道具&#xff0c;每用一次可以让一个边的花费1&…

.Net开发者必知的技术类RSS订阅指南

作为一个.Net开发者&#xff0c;在如今这个信息大爆炸时代&#xff0c;网络上.net开发方面的信息浩如烟海(获取信息的渠道很多&#xff0c;比如各种 APP、公众号、聚合信息网站、博客园、InfoQ等等)&#xff0c;如何用有限的时间来获取并消化有效信息显得格外重要。虽然我们获取…

又一最大子段和

又一最大子段和&#xff08;牛客小白月赛38 &#xff09; 题意&#xff1a; 我们将一个数列{an}的最大字段和的值记为S(a),现在你可以对进行若干次操作&#xff0c;每次操作&#xff0c;你可以选择数列中的一个数字&#xff0c;将其改为[−10100,10100][-10^{100},10^{100}][…

我的微服务观,surging 2.0将会带来多大的改变

Surging 自2017年6月16日开源以来&#xff0c;已收到不少公司的关注或者使用&#xff0c;其中既有以海克斯康超大型等外企的关注&#xff0c;也不乏深圳泓达康、重庆金翅膀等传统行业的正式使用&#xff0c;自2019年年初&#xff0c;surging2.0 便已正式进入研发阶段&#xff0…