从零开始实现ASP.NET Core MVC的插件式开发(五) - 插件的删除和升级

本文介绍了如何在ASP.NET Core 3.0中使用AssemblyLoadContext解决.NET Core 2.2中插件删除和升级的问题。通过创建可回收的程序集加载上下文,实现了插件的删除、升级功能,克服了文件被占用的障碍。文章详细阐述了整个过程中的挑战和解决方案,包括从.NET Core 2.2的遗留问题到.NET Core 3.0的AssemblyLoadContext使用,以及最终实现的插件删除和升级的代码。
摘要由CSDN通过智能技术生成

标题:从零开始实现ASP.NET Core MVC的插件式开发(五) - 使用AssemblyLoadContext实现插件的升级和删除
作者:Lamond Lu
地址:https://www.cnblogs.com/lwqlun/p/11395828.html
源代码:https://github.com/lamondlu/DynamicPlugins

前景回顾:

简介

在上一篇中,我为大家讲解了如何实现插件的安装,在文章的最后,留下了两个待解决的问题。

  • .NET Core 2.2中不能实现运行时删除插件
  • .NET Core 2.2中不能实现运行时升级插件

其实这2个问题归根结底其实都是一个问题,就是插件程序集被占用,不能在运行时更换程序集。在本篇中,我将分享一下我是如何一步一步解决这个问题的,其中也绕了不少弯路,查阅过资料,在.NET Core官方提过Bug,几次差点想放弃了,不过最终是找到一个可行的方案。

.NET Core 2.2的遗留问题

程序集被占用的原因

回顾一下,我们之前加载插件程序集时所有使用的代码。

	var provider = services.BuildServiceProvider();
    using (var scope = provider.CreateScope())
    {
    	var unitOfWork = scope.ServiceProvider.GetService<IUnitOfWork>();
        var allEnabledPlugins = unitOfWork.PluginRepository
        	.GetAllEnabledPlugins();

		foreach (var plugin in allEnabledPlugins)
        {
        	var moduleName = plugin.Name;
            var assembly = Assembly.LoadFile($"{AppDomain.CurrentDomain.BaseDirectory}Modules\\{moduleName}\\{moduleName}.dll");

			var controllerAssemblyPart = new AssemblyPart(assembly);
            mvcBuilders.PartManager
                    .ApplicationParts
                    .Add(controllerAssemblyPart);
        }
    }

这里我们使用了Assembly.LoadFile方法加载了插件程序集。 在.NET中使用Assembly.LoadFile方法加载的程序集会被自动锁定,不能执行任何转移,删除等造作,所以这就给我们删除和升级插件造成了很大困难。

PS: 升级插件需要覆盖已加载的插件程序集,由于程序集锁定,所以覆盖操作不能成功。

使用AssemblyLoadContext

在.NET Framework中,如果遇到这个问题,常用的解决方案是使用AppDomain类来实现插件热插拔,但是在.NET Core中没有AppDomain类。不过经过查阅,.NET Core 2.0之后引入了一个AssemblyLoadContext类来替代.NET Freamwork中的AppDomain。本以为使用它就能解决当前程序集占用的问题,结果没想到.NET Core 2.x版本提供的AssemblyLoadContext没有提供Unload方法来释放加载的程序集,只有在.NET Core 3.0版本中才为AssemblyLoadContext类添加了Unload方法。

相关链接:

升级.NET Core 3.0 Preview 8

因此,为了完成插件的删除和升级功能,我将整个项目升级到了最新的.NET Core 3.0 Preview 8版本。

这里.NET Core 2.2升级到.NET Core 3.0有一点需要注意的问题。

在.NET Core 2.2中默认启用了Razor视图的运行时编译,简单点说就是.NET Core 2.2中自动启用了读取原始的Razor视图文件,并编译视图的功能。这就是我们在第三章和第四章中的实现方法,每个插件文件最终都放置在了一个Modules目录中,每个插件既有包含Controller/Action的程序集,又有对应的原始Razor视图目录Views,在.NET Core 2.2中当我们在运行时启用一个组件之后,对应的Views可以自动加载。

The files tree is:
=================

  |__ DynamicPlugins.Core.dll
  |__ DynamicPlugins.Core.pdb
  |__ DynamicPluginsDemoSite.deps.json
  |__ DynamicPluginsDemoSite.dll
  |__ DynamicPluginsDemoSite.pdb
  |__ DynamicPluginsDemoSite.runtimeconfig.dev.json
  |__ DynamicPluginsDemoSite.runtimeconfig.json
  |__ DynamicPluginsDemoSite.Views.dll
  |__ DynamicPluginsDemoSite.Views.pdb
  |__ Modules
    |__ DemoPlugin1
      |__ DemoPlugin1.dll
      |__ Views
        |__ Plugin1
          |__ HelloWorld.cshtml
        |__ _ViewStart.cshtml

但是在.NET Core 3.0中,Razor视图的运行时编译需要引入程序集Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation。并且在程序启动时,需要启动运行时编译的功能。

public void ConfigureServices(IServiceCollection services)
{
    ...
	var mvcBuilders = services.AddMvc()
        .AddRazorRuntimeCompilation();
    
    ...
}

如果没有启用Razor视图的运行时编译,程序访问插件视图的时候,就会报错,提示视图找不到。

使用.NET Core 3.0的AssemblyLoadContext加载程序集

这里为了创建一个可回收的程序集加载上下文,我们首先基于AssemblyLoadcontext创建一个CollectibleAssemblyLoadContext类。其中我们将IsCollectible属性通过父类构造函数,将其设置为true。

	public class CollectibleAssemblyLoadContext 
        : AssemblyLoadContext
    {
        public CollectibleAssemblyLoadContext() 
        	: base(isCollectible: true)
        {
        }

        protected override Assembly Load(AssemblyName name)
        {
            return nul
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值