最近工做都用 .NET Core Mvc 作開發,工做中遇到了比較特殊的需求:能不能在不中止網站,不重啓,在網站運行中加載新的 Controller 呢?git
基於這個需求,分析下來,那就是動態加載一個已經編譯好的 dll 文件到當前運行時中,不用想,確定使用到反射,讀取類型,把 Controller 類型的類加載到 MVC 的 controller 集合中。 github
嗯,中國特點思惟,先百度谷歌看看有麼有現成的吧!結果百度來谷歌去,沒有找到相關內容,都怪 .NET Core 太新了,網絡上尚未太多相關的東西,只找到一個提問 :編程
ASP.Net Core register Controller at runtime設計模式
https://stackoverflow.com/questions/46156649/asp-net-core-register-controller-at-runtime瀏覽器
注:裏面有個人回答哦 => cnxiaoby緩存
基於這個提問,咱們知道了,ApplicationPart 是用來管理運行時中加載的 dll 的,只要能把帶有Controller的dll 加載到ApplicationParts,刷新一下相關的 runtime 就能實現了吧。有了思路,就先看看 .NET Core MVC 源碼吧,從裏面找找看有沒有相關的 Controller 緩存的集合,看能不能動態加載進去。 網絡
因爲工做忙,斷斷續續看了幾天源碼,過程波折就不細說了,最終找到了:ide
https://github.com/aspnet/Mvc/blob/rel/2.0.0/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionDescriptorCollectionProvider.cs網站
比較關鍵的幾個地方:編碼
public ActionDescriptorCollectionProvider( IEnumerable<IActionDescriptorProvider> actionDescriptorProviders, IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders) { _actionDescriptorProviders = actionDescriptorProviders .OrderBy(p => p.Order) .ToArray(); _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray(); ChangeToken.OnChange( GetCompositeChangeToken, UpdateCollection); } private IChangeToken GetCompositeChangeToken() { if (_actionDescriptorChangeProviders.Length == 1) { return _actionDescriptorChangeProviders[0].GetChangeToken(); } var changeTokens = new IChangeToken[_actionDescriptorChangeProviders.Length]; for (var i = 0; i < _actionDescriptorChangeProviders.Length; i++) { changeTokens[i] = _actionDescriptorChangeProviders[i].GetChangeToken(); } return new CompositeChangeToken(changeTokens); } public ActionDescriptorCollection ActionDescriptors { get { if (_collection == null) { UpdateCollection(); } return _collection; } }
首先是屬性 ActionDescriptors ,它在 Controller/Action 的匹配中會用到,
其次是方法調用:ChangeToken.OnChange(GetCompositeChangeToken, UpdateCollection); 一開始我也不懂ChangeToken,你們自行搜索,按照代碼字面意思,這裏註冊了一個 變動的 ,觸發條件是 GetCompositeChangeToken 這個方法的返回值,觸發變動操做的方法是 UpdateColection ,正好是更新 ActionDescriptors 集合,原來 .NET Core 早就幫咱們作好了功能了!!再次證實 .NET Core 2.0 的強大。
找到了關鍵點,那咱們就開始實驗吧:
具體代碼以下:
第一步:實現 IActionDescriptorChangeProvider 接口類:
public class MyActionDescriptorChangeProvider : IActionDescriptorChangeProvider { public static MyActionDescriptorChangeProvider Instance { get; } = new MyActionDescriptorChangeProvider(); public CancellationTokenSource TokenSource { get; private set; } public bool HasChanged { get; set; } public IChangeToken GetChangeToken() { TokenSource = new CancellationTokenSource(); return new CancellationChangeToken(TokenSource.Token); } }
第二步:把實現類,在 Startup 中註冊到 Services 中:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance); services.AddSingleton(MyActionDescriptorChangeProvider.Instance); }
第三步:在運行過程當中加載 dll ,加載編譯好的 Controller
public class TestController : Controller { private readonly ApplicationPartManager _partManager; private readonly IHostingEnvironment _hostingEnvironment; public TestController( ApplicationPartManager partManager, IHostingEnvironment env) { _partManager = partManager; _hostingEnvironment = env; } public IActionResult RegisterControllerAtRuntime() { string assemblyPath = _hostingEnvironment.ContentRootPath + @"\DLL\Test.dll"; var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); if (assembly != null) { _partManager.ApplicationParts.Add(new AssemblyPart(assembly)); // Notify change MyActionDescriptorChangeProvider.Instance.HasChanged = true; MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel(); return Content("1"); } return Content("0"); } }
Test.dll 中的源碼:
public class HomeController : Controller { public IActionResult Index() { return Content("Test Home Index"); } }
運行
瀏覽器訪問 http://localhost:81/Home/Index,返回 404,
瀏覽器訪問:http://localhost:81/Test/RegisterControllerAtRuntime ,返回 1,說明執行成功,
再訪問 http://localhost:81/Home/Index,返回 Test Home Index ,說明大功告成。
拓展:
基於這個需求,咱們能夠實現網站動態更新的功能了,或者其餘腦洞更大的功能。
總結:
多讀讀源碼,瞭解它的原理,會對咱們編程有很大的幫助,並且還能學到不少設計模式、範式,學到好的編碼規範、命名習慣。讓咱們讀源碼像讀書同樣日常!
後續發現:
https://github.com/aspnet/Mvc/blob/rel/2.0.0/test/WebSites/ApiExplorerWebSite/Startup.cs
https://github.com/aspnet/Mvc/blob/rel/2.0.0/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerReloadableController.cs
這裏已經有相關相似用法示例了