您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關怎么在.Net Core中配置Json自動更新,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
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很重要,后面要考:smile:.
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
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
方法什么執行的:joy:.
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實例,所以直覺告訴我,在它的構造函數內必有貓膩:confused:.
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,生產者,消費者,不知道看到這個關鍵詞想到的是什么,反正我想到的是小學時學習食物鏈時的:snake:與:rat:.
那么我們來看看這里的:snake:是什么,:rat:又是什么,還得回到 FileConfigurationProvider
的構造函數.
可以看到生產者:rat:是:
() => Source.FileProvider.Watch(Source.Path)
消費者:snake:是:
() => { Thread.Sleep(Source.ReloadDelay); Load(reload: true); }
我們想一下,一旦有一條:rat:跑出來,就立馬被:snake:吃了,
那我們這里也一樣,一旦有FileProvider.Watch返回了什么東西,就會發生Load()事件來重新加載數據.
:snake:與:rat:好理解,可是代碼就沒那么好理解了,我們通過 OnChange
的第一個參數 Func<IChangeToken> changeTokenProducer
方法知道,這里的:rat:,其實是 IChangeToken
.
IChangeToken
public interface IChangeToken { bool HasChanged { get; } bool ActiveChangeCallbacks { get; } IDisposable RegisterChangeCallback(Action<object> callback, object state); }
IChangeToken的重點在于里面有個RegisterChangeCallback方法,:snake:吃:rat:的這件事,就發生在這回調方法里面.
我們來做個:snake:吃:rat:的實驗.
實驗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
里做了什么,總體來說三步.
1.獲取一個新的token.
2.調用消費者進行消費.
3.給新獲取的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)); }
以上就是怎么在.Net Core中配置Json自動更新,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。