从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件

标题:从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件
作者:Lamond Lu
地址:https://www.cnblogs.com/lwqlun/p/11260750.html
源代码:https://github.com/lamondlu/DynamicPlugins

从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件

前情回顾回顾

从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用Application Part动态加载控制器和视图

从零开始实现ASP.NET Core MVC的插件式开发(二) - 如何创建项目模板

在前面两篇中,我为大家演示了如何使用Application Part动态加载控制器和视图,以及如何创建插件模板来简化操作。
在上一篇写完之后,我突然想到了一个问题,如果像前两篇所设计那个来构建一个插件式系统,会有一个很严重的问题,即

当你添加一个插件之后,整个程序不能立刻启用该插件,只有当重启整个ASP.NET Core应用之后,才能正确的加载插件。因为所有插件的加载都是在程序启动时ConfigureService方法中配置的。

这种方式的插件系统会很难用,我们期望的效果是在运行时动态启用和禁用插件,那么有没有什么解决方案呢?答案是肯定的。下面呢,我将一步一步说明一下自己的思路、编码中遇到的问题,以及这些问题的解决方案。

为了完成这个功能,我走了许多弯路,当前这个方案可能不是最好的,但是确实是一个可行的方案,如果大家有更好的方案,我们可以一起讨论一下。

在Action中激活组件

当遇到这个问题的时候,我的第一思路就是将ApplicationPartManager加载插件库的代码移动到某个Action中。于是我就在主站点中创建了一个PluginsController, 并在启用添加了一个名为Enable的Action方法。

public class PluginsController : Controller { public IActionResult Enable() { var assembly = Assembly.LoadFile(AppDomain.CurrentDomain.BaseDirectory + "DemoPlugin1\\DemoPlugin1.dll"); var viewAssembly = Assembly.LoadFile(AppDomain.CurrentDomain.BaseDirectory + "DemoPlugin1\\DemoPlugin1.Views.dll"); var viewAssemblyPart = new CompiledRazorAssemblyPart(viewAssembly); var controllerAssemblyPart = new AssemblyPart(assembly); _partManager.ApplicationParts.Add(controllerAssemblyPart); _partManager.ApplicationParts.Add(viewAssemblyPart); return Content("Enabled"); } }

修改代码之后,运行程序,这里我们首先调用/Plugins/Enable来尝试激活组件,激活之后,我们再次调用/Plugin1/HelloWorld

这里会发现程序返回了404, 即控制器和视图没有正确的激活。

从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件

这里你可能有疑问,为什么会激活失败呢?

这里的原因是,只有当ASP.NET Core应用启动时,才会去ApplicationPart管理器中加载控制器与视图的程序集,所以虽然新的控制器程序集在运行时被添加到了ApplicationPart管理器中,但是ASP.NET Core不会自动进行更新操作,所以这里我们需要寻找一种方式能够让ASP.NET Core重新加载控制器的方法。

通过查询各种资料,我最终找到了一个切入点,在ASP.NET Core 2.2中有一个类是ActionDescriptorCollectionProvider,它的子类DefaultActionDescriptorCollectionProvider是用来配置Controller和Action的。

源代码:

internal class DefaultActionDescriptorCollectionProvider : ActionDescriptorCollectionProvider { private readonly IActionDescriptorProvider[] _actionDescriptorProviders; private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders; private readonly object _lock; private ActionDescriptorCollection _collection; private IChangeToken _changeToken; private CancellationTokenSource _cancellationTokenSource; private int _version = 0; public DefaultActionDescriptorCollectionProvider( IEnumerable<IActionDescriptorProvider> actionDescriptorProviders, IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders) { ... ChangeToken.OnChange( GetCompositeChangeToken, UpdateCollection); } public override ActionDescriptorCollection ActionDescriptors { get { Initialize(); return _collection; } } ... 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); } ... private void UpdateCollection() { lock (_lock) { var context = new ActionDescriptorProviderContext(); for (var i = 0; i < _actionDescriptorProviders.Length; i++) { _actionDescriptorProviders[i].OnProvidersExecuting(context); } for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--) { _actionDescriptorProviders[i].OnProvidersExecuted(context); } var oldCancellationTokenSource = _cancellationTokenSource; _collection = new ActionDescriptorCollection( new ReadOnlyCollection<ActionDescriptor>(context.Results), _version++); _cancellationTokenSource = new CancellationTokenSource(); _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); oldCancellationTokenSource?.Cancel(); } } }

这里ActionDescriptors属性中记录了当ASP.NET Core程序启动后,匹配到的所有Controller/Action集合。

UpdateCollection方法使用来更新ActionDescriptors集合的。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwyjwg.html