最近工做都用 .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
这里已经有相关相似用法示例了