前言:

 之前在文章- AppDomain实现【插件式】开发 中介绍了在 .NET Framework 中,通过AppDomain实现动态加载和卸载程序集的效果。

 但是.NET Core 仅支持单个默认应用域,那么在.NET Core中如何实现【插件式】开发呢?

一、.NET Core 中 AssemblyLoadContext的使用

 1、AssemblyLoadContext简介:

  每个 .NET Core 应用程序均隐式使用 AssemblyLoadContext。 它是运行时的提供程序,用于定位和加载依赖项。 只要加载了依赖项,就会调用 AssemblyLoadContext 实例来定位该依赖项。

  • 它提供定位、加载和缓存托管程序集和其他依赖项的服务。

  • 为了支持动态代码加载和卸载,它创建了一个独立上下文,用于在其自己的 AssemblyLoadContext 实例中加载代码及其依赖项。

 2、AssemblyLoadContext和AppDomain卸载差异:

  使用 AssemblyLoadContext 和使用 AppDomain 进行卸载之间存在一个值得注意的差异。 对于 Appdomain,卸载为强制执行。

  卸载时,会中止目标 AppDomain 中运行的所有线程,会销毁目标 AppDomain 中创建的托管 COM 对象,等等。 对于 AssemblyLoadContext,卸载是“协作式的”。

  调用 AssemblyLoadContext.Unload 方法只是为了启动卸载。以下目标达成后,卸载完成:

  • 没有线程将程序集中的方法加载到其调用堆栈上的 AssemblyLoadContext 中。
  • 程序集中的任何类型都不会加载到 AssemblyLoadContext,这些类型的实例本身由以下引用:
    • AssemblyLoadContext 外部的引用,弱引用(WeakReference 或 WeakReference<T>)除外。
    • AssemblyLoadContext 内部和外部的强垃圾回收器 (GC) 句柄(GCHandleType.Normal 或 GCHandleType.Pinned)。  

二、.NET Core 插件式方式实现

 1、创建可卸载的上下文PluginAssemblyLoadContext

class PluginAssemblyLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver; /// <summary>
/// 构造函数
/// isCollectible: true 重点,允许Unload
/// </summary>
/// <param name="pluginPath"></param>
public PluginAssemblyLoadContext(string pluginPath) : base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
} protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
} protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
}

 2、创建插件接口及实现

  整体项目结构为:

  

  a)添加项目PluginInterface,插件接口:

public interface IPlugin
{
string Name { get; }
string Description { get; }
string Execute(object inPars);
}

  b)添加HelloPlugin项目,实现不引用外部dll插件

public class HelloPlugin : PluginInterface.IPlugin
{
public string Name => "HelloPlugin";
public string Description { get => "Displays hello message."; }
public string Execute(object inPars)
{return ("Hello !!!" + inPars?.ToString());
   }
}

  c)添加JsonPlugin项目,实现引用三方dll插件

public class JsonPlugin : PluginInterface.IPlugin
{
public string Name => "JsonPlugin";
public string Description => "Outputs JSON value.";
private struct Info
{
public string JsonVersion;
public string JsonLocation;
public string Machine;
public DateTime Date;
}
public string Execute(object inPars)
{
Assembly jsonAssembly = typeof(JsonConvert).Assembly;
Info info = new Info()
{
JsonVersion = jsonAssembly.FullName,
JsonLocation = jsonAssembly.Location,
Machine = Environment.MachineName,
Date = DateTime.Now
};
return JsonConvert.SerializeObject(info, Formatting.Indented);
}
}

  d)添加PluginsApp项目,实现调用插件方法:

  修改窗体界面布局:

  

  添加执行方法

/// <summary>
/// 将此方法标记为noinline很重要,否则JIT可能会决定将其内联到Main方法中。
/// 这可能会阻止成功卸载插件,因为某些实例的生存期可能会延长到预期卸载插件的时间点之外。
/// </summary>
/// <param name="assemblyPath"></param>
/// <param name="inPars"></param>
/// <param name="alcWeakRef"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
static string ExecuteAndUnload(string assemblyPath, object inPars, out WeakReference alcWeakRef)
{
string resultString = string.Empty;
// 创建 PluginLoadContext对象
var alc = new PluginAssemblyLoadContext(assemblyPath); //创建一个对AssemblyLoadContext的弱引用,允许我们检测卸载何时完成
alcWeakRef = new WeakReference(alc); // 加载程序到上下文
// 注意:路径必须为绝对路径.
Assembly assembly = alc.LoadFromAssemblyPath(assemblyPath); //创建插件对象并调用
foreach (Type type in assembly.GetTypes())
{
if (typeof(IPlugin).IsAssignableFrom(type))
{
IPlugin result = Activator.CreateInstance(type) as IPlugin;
if (result != null)
{
resultString = result.Execute(inPars);
break;
}
}
}
//卸载程序集上下文
alc.Unload();
return resultString;
}

三、效果验证

 1、非引用外部dll的插件执行:执行后对应dll成功卸载,程序集数量未增加。

  

 2、引用外部包的插件:执行后对应dll未卸载,程序集数量增加。

  

  通过监视查看对象状态:该上下文在卸载中。暂未找到原因卸载失败(疑问?)

  

四、总结:

 虽然微软文档说.Net Core中使用AssemblyLoadContext来实现程序集的加载及卸载实现,但通过验证在加载引用外部dll后,加载后不能正常卸载。或者使用方式还不正确。

 源码地址

 参考:https://docs.microsoft.com/zh-cn/dotnet/standard/assembly/unloadability

 

 

.NET Core中插件式开发实现的更多相关文章

  1. 零基础ASP.NET Core MVC插件式开发

    零基础ASP.NET Core MVC插件式开发 一个项目随着业务模块的不断增加,系统会越来越庞大.如果参与开发的人员越多,管理起来也难度也很大.面对这样的情况,首先想到的是模块化插件式开发,根据业务 ...

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

    标题:从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用Application Part动态加载控制器和视图 作者:Lamond Lu 地址:http://www.cnblogs ...

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

    标题:从零开始实现ASP.NET Core MVC的插件式开发(二) - 如何创建项目模板 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/11155 ...

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

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

  5. 从零开始实现ASP.NET Core MVC的插件式开发(四) - 插件安装

    标题:从零开始实现ASP.NET Core MVC的插件式开发(四) - 插件安装 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/11260750. ...

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

    标题:从零开始实现ASP.NET Core MVC的插件式开发(五) - 使用AssemblyLoadContext实现插件的升级和删除 作者:Lamond Lu 地址:https://www.cnb ...

  7. 从零开始实现ASP.NET Core MVC的插件式开发(六) - 如何加载插件引用

    标题:从零开始实现ASP.NET Core MVC的插件式开发(六) - 如何加载插件引用. 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/1171 ...

  8. 从零开始实现ASP.NET Core MVC的插件式开发(七) - 近期问题汇总及部分解决方案

    标题:从零开始实现ASP.NET Core MVC的插件式开发(七) - 问题汇总及部分解决方案 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/12 ...

  9. 从零开始实现ASP.NET Core MVC的插件式开发(八) - Razor视图相关问题及解决方案

    标题:从零开始实现ASP.NET Core MVC的插件式开发(八) - Razor视图相关问题及解决方案 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun ...

随机推荐

  1. 【学习底层原理系列】重读spring源码3-加载beanDefinition的方法obtainFreshBeanFactory

    obtainFreshBeanFactory()方法概述 定义BeanFactory,并加载以下两种bean的定义,装配到BeanFactory: 1.配置文件中定义的bean 2.通过<con ...

  2. Day05_18_类和对象的含义与关系

    Java 类和对象 类的含义? 类属于引用数据类型,java语言中所有的.class都属于引用数据类型, 在类体当中,方法体之外定义的变量被称为 成员变量,成员变量若没有赋值,系统会默认赋值为0: 先 ...

  3. Ansible(1)- 简单介绍

    什么是 Ansible 开源部署工具,也是一个自动化运维工具 开发语言:Python Ansible 的特性 模块化部署管理:调用特定的模块,完成特定任务 三个关键模块:Paramiko(python ...

  4. vue中利用.env文件存储全局环境变量,以及配置vue启动和打包命令

    目录 1,前言 2,.env文件的作用 3,配置.env文件 4,配置启动命令 5,获取.env中的全局变量 5,实际用处 1,前言 分享一下vue项目中利用.env文件存储全局环境变量,以及利于项目 ...

  5. odoo字段属性列举

    罗列一些Odoo中的字段属性,基本包含大部分的属性. 常用字段属性 平平无奇常用字段属性 string:字段的标题,在UI视图标签中使用.它是可选项,如未设置,会通过首字母大写及将空格替换成为下划线来 ...

  6. 【Springboot】Springboot自动装配原理

    1.核心注解就是 EnableAutoConfiguration  该注解会激活SpringBoot的自动装配功能: 代码如下: @Target(ElementType.TYPE) @Retentio ...

  7. POJ3114强连通+spfa

    题意:       给你n个点,m条有向边,q询问,每次询问给两个数a,b输出a->b的最短路,但是题目有个限制,就是在一个环上的任意两点距离为0. 思路:       简单题目,直接强连通压缩 ...

  8. Linux系统登录相关

    whoami:查看当前用户 who:查看当前登录系统的所有用户 tty指的是主机的图形化界面的面板 pts/x指的是远程ssh连接的窗口 who -b:主机的上一次启动时间 w:显示已经登陆系统的用户 ...

  9. Redis数据结构—链表与字典的结构

    目录 Redis数据结构-链表与字典的结构 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 Redis字典的使用 Re ...

  10. Filter过滤器的基本使用方法

    ProjectDescription Filter的使用 创建类实现javax.servlet.Filter. 重写方法: init(); //过滤器初始化 doFilter(); //过滤请求 1. ...