Loading

.NET Conf 2020 - 基于ASP.NET Core构建可热插拔的插件化系统

文章标题: .NET Conf 2020 - 基于ASP.NET Core构建可热插拔的插件化系统
作者: Lamond Lu
项目地址:https://github.com/lamondlu/CoolCat
博客:http://www.cnblogs.com/lwqlun

以下是2020.12.19日的演讲文稿和视频:

大家好,我是陆楠,我来自北京盛安德科技发展有限公司青岛分公司,很高兴能参加本次.NET开发者大会,今天我分享的主题是《基于ASP.NET Core构建可热插拔的插件化系统》。

插件化架构,又称微核架构,指的是软件的内核相对较小,主要功能和业务逻辑都通过插件实现的架构。

插件化架构一般有两个核心概念:

  • 内核
  • 插件

内核通常只包含系统运行的最小功能,以及定义插件必须符合的接口;插件则是互相独立的模块,一般只包含单一的功能。

插件化技术并不是一个新兴的技术,早期很多基于COM开发的WIN32程序其实都是插件化的系统。在.NET/.NET Core中,也有许多插件化的实现方案,例如,开源框架ABP, 开源的内容管理系统DotNetNuke, 电子商务框架NopCommerce。

在设计插件化方案的时候,我们需要考虑一下几个问题:

  • 如何隔离插件
  • 如何实现插件之间的通讯
  • 如何实现热插拔

在.NET Framework时代,我们最常用的方案是使用AppDomain应用程序域来封装插件。使用AppDomain, 我们可以将不同的插件隔离在不同的应用程序域中。至于插件与插件之间的通讯,我们可以借助MarshalByRefObject类来实现。至于热插拔,我们可以通过AppDomain自带的Load/Unload方法来完成,非常的简单。

但是到了.NET Core中,情况大不相同了。主要的原因是.NET Core中已经将AppDomain的概念移除了,那么我们该如何实现插件化呢?

这里我们首先要介绍的是ASP.NET Core中新引入的功能AssemblyLoadContext, 简称ALC, ALC提供了一个类似AppDomain的隔离区域,你可以通过ALC来加载程序集,每个ALC加载的程序及之间互不干扰。

这里请注意,正是由于这种设计,如果将一个程序集引入到两个不同ALC中,运行时会认为他们是不同的程序集。

除了自定义的ALC, 在每个ASP.NET Core应用启动的时候,运行时都会创建一个默认的ALC, 这里我们需要了解自定义ALC和默认ALC的加载顺序

  • 当自定义ALC中的某个插件使用某个程序集的时候,会优先查找当前插件所在的自定义的ALC,如果找不到该程序集,则会进一步查找默认ALC, 所以ALC的程序集加载会优先于默认ALC

我们前面说过,不同的ALC应用相同的程序集,运行时会认为他们是不同的程序集,所以当两个插件使用相同程序集的,我们最好将这个程序集加载到默认ALC中,否则在插件交互的时候可能会出现类型冲突。

除了AssemblyLoadContext, 为了实现插件化,微软在ASP.NET Core中还提供了另外一个高级特性Application Part - 应用组件。

Application Part并不算一个新特性,因为在它在.NET Core 2.x版本中就已经被引入了,但是可能部分开发人员没有过或了解过它。Application Part为ASP.NET Core提供了强大的复用能力,使用Application Part, ASP.NET Core可以从程序集中发现控制器、视图组件、Razor预编译视图、Tag Helper等功能,再借助Application Part Manager, 这些已经编译好的功能组件就可以在其它项目中直接复用了,这就极大提高了ASP.NET Core功能组件的可复用行。

基于AssemblyLoadContextApplication Part, 你就可以轻松的实现ASP.NET Core的插件化了,但是如果想要在ASP.NET Core中实现一个可热插拔的插件化组件系统,我们还需要针对ASP.NET Core解决很多适配性问题,例如:

  • 如果在运行时加载预编译视图?
  • 如果在运行时刷新路由和Controler/Action的映射关系?
  • 一个组件如何从另外一个组件拉取数据
  • 一个组件如何向另外一个组件发送消息通知,完成进一步的业务操作

为了简化这一部分的复杂度,我搭建了一个开源项目CoolCat, CoolCat默认支持.NET Core 3.1和.NET 5。

CoolCat已经实现了一下的特性

  • 插件的安装升级
  • 运行时热插拔插件
  • 插件间通讯
  • 类Swagger的插件文档
  • 支持容器化

以下是CoolCat的整体架构图:

这里主程序为CoolCat, 所有的插件都通过ALC加载到主程序中,主程序中定义了通知中心、文档中心、查询中心

  • 通知中心负责跨插件的消息通知
  • 文档中心负责生成插件的文档
  • 查询中心负责跨插件的数据查询

并且为了简化创建插件项目,我创建了一个CoolCat插件模板,你可以通过dotnet new命令来安装插件模板,并安装模板项目。

dotnet new –i `CoolCat`Module
dotnet new `CoolCat`Module –n {projectName}

这里创建出的插件项目和一个普通MVC项目相差无几,比较特殊的是项目会自动生成一个plugin.json文件,里面包含了当前插件的基本信息。


如果这个项目的生成文件打包,那么它就是一个可移动的插件安装包了。

下面呢,我们就通过一个简单的Demo, 给大家演示一下CoolCat项目的基本功能。

这里我们首先使用dotnet run命令启动当前CoolCat, 当程序第一次启动的时候,会自动建表。这里呢,我使用了FluentMigrator作为数据库迁移工具,所以在项目启动时,可以进行自动的脚本迁移。

项目启动之后,我们就可以从浏览器打开这个项目了。项目的默认界面是安装界面,我们可以在这个界面上选择一些预定义的插件,完成安装

当然也不仅限于此,如果你想自定义一个其他的预安装插件,你可以将这些插件放置在项目下的PresetModules目录中。

这里呢,我们就选择默认的2个插件,完成安装。

安装完成之后,我们就会自动进入CoolCat的主界面。这里我们可以通过System菜单下的Plugins子菜单来管理插件。

现在呢,我们来模拟一个场景,假设我们当前开发了2个插件,一个是图书库存插件,一个是图书租借插件。租借插件的数据来源是库存插件。并且当某本图书从租借插件租出之后,库存插件中的当前图书的状态也应该变为出库状态。

这里我们首先通过CoolCat来安装这2个插件。安装完成之后,我们启用插件,这里大家会发现,当我们启动插件之后,顶部导航栏中会自动出现这个插件的菜单,这说明我们的热插拔功能正确的引导并加载的插件。

下一步,我们进入库存插件,添加一本图书C#, 添加完成之后,我们会发现这本书的默认状态是入库状态。

现在我们打开图书租借功能,我们会发现入库状态的图书,正确的显示在了可租借图书列表界面,这说明我们的跨插件拉取数据成功。

这时候,我们选择租出这本书,点击Rent按钮,操作完成之后,这本书就从可租借列表中消失了。

下面我们回到图书库存插件,你会发现图书库存的状态已经变为出库,这是说明我们的跨插件消息传输成功了,当图书租出之后,后续的出库操作自动完成。

至此,我们就完成了这个简单的Demo

如果大家感兴趣的话,可以下载CoolCat项目自行体验一下,针对整个框架的研究过程和其中遇到的问题,我都写在了我的博客园站点中,大家可以自行查看。如果遇到问题或者想参与到本项目中,可以给我发邮件,也可以在博客园给我留言。

以上就是我今天分享的全部内容,谢谢大家。

posted @ 2020-12-20 17:10  LamondLu  阅读(526)  评论(0编辑  收藏