.NET Core中插件式开发实现
前言:
之前在文章- 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中插件式开发实现的更多相关文章
- 零基础ASP.NET Core MVC插件式开发
零基础ASP.NET Core MVC插件式开发 一个项目随着业务模块的不断增加,系统会越来越庞大.如果参与开发的人员越多,管理起来也难度也很大.面对这样的情况,首先想到的是模块化插件式开发,根据业务 ...
- 从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用ApplicationPart动态加载控制器和视图
标题:从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用Application Part动态加载控制器和视图 作者:Lamond Lu 地址:http://www.cnblogs ...
- 从零开始实现ASP.NET Core MVC的插件式开发(二) - 如何创建项目模板
标题:从零开始实现ASP.NET Core MVC的插件式开发(二) - 如何创建项目模板 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/11155 ...
- 从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件
标题:从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/112 ...
- 从零开始实现ASP.NET Core MVC的插件式开发(四) - 插件安装
标题:从零开始实现ASP.NET Core MVC的插件式开发(四) - 插件安装 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/11260750. ...
- 从零开始实现ASP.NET Core MVC的插件式开发(五) - 插件的删除和升级
标题:从零开始实现ASP.NET Core MVC的插件式开发(五) - 使用AssemblyLoadContext实现插件的升级和删除 作者:Lamond Lu 地址:https://www.cnb ...
- 从零开始实现ASP.NET Core MVC的插件式开发(六) - 如何加载插件引用
标题:从零开始实现ASP.NET Core MVC的插件式开发(六) - 如何加载插件引用. 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/1171 ...
- 从零开始实现ASP.NET Core MVC的插件式开发(七) - 近期问题汇总及部分解决方案
标题:从零开始实现ASP.NET Core MVC的插件式开发(七) - 问题汇总及部分解决方案 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/12 ...
- 从零开始实现ASP.NET Core MVC的插件式开发(八) - Razor视图相关问题及解决方案
标题:从零开始实现ASP.NET Core MVC的插件式开发(八) - Razor视图相关问题及解决方案 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun ...
随机推荐
- day10.闭包函数与装饰器
一.闭包函数 1.闭函数:被封闭起来的函数==>定义在函数内部的函数,特点是只能在函数内调用 2.包函数:该函数引用了一个名字,该名字来自于E这一层 总结:闭包函数指的是定义在函数内部的函数引用 ...
- 我最近做了一个react的后台管理系统,用于快速创建后台项目模板
react-ant-admin 此框架使用与二次开发,前端框架使用react,UI框架使用ant-design,全局数据状态管理使用redux,ajax使用库为axios.用于快速搭建中后台页面.欢迎 ...
- matlab map容器类型
matlab map容器类型 map容器类型以及map类概述 map是将一个量映射到另一个量上,此是前面的量就是map的键(key),后面的量就是map的数据(value).map的键和对应的数据都储 ...
- Day13_70_join()
join() 方法 * 合并线程 join()线程合并方法出现在哪,就会和哪个线程合并 (此处是thread和主线程合并), * 合并之后变成了单线程,主线程需要等thread线程执行完毕后再执行,两 ...
- @valid和自定义异常
@valid和自定义异常 问题的产生: 当有很多参数需要校验时,比如name,age,email等很多参数都需要判空,或者有长度限制时,如果后端写很多if-else就有很多代码,不美观,不优雅.前端每 ...
- python 闭包函数与装饰器
1.什么是闭包函数 (1):什么是闭包函数: #内部函数包含对外部作用域而非全局作用域的引用, 简而言之, 闭包的特点就是内部函数引用了外部函数中的变量. 在Python中,支持将函数当做对象使用,也 ...
- c协程库libco几点体会
https://www.cnblogs.com/dearplain/p/9820913.html 这里说的是Tencent开源的libco. libco的用途和依赖 主要还是c/c++服务端,相比li ...
- 漫画 | 公司测试因提Bug不规范,锒铛入狱~
互联网人罪状系列 1.上班第一天,前端把后端告上县衙,还列了 5 宗罪 2. 程序员状告产品经理八大罪状 (上) 3.程序员状告产品经理八大罪状(下) 开发人员与测试人员的关系,就如同程序员与产品经理 ...
- 关于height:100%
要想高度百分比起作用,一般来说,要满足两个条件:其一,父标签有高度可寻,就是向上遍历父标签要找到一个定值高度(body,html另外讨论),如果中途有个height为auto或是没有设置height属 ...
- 进程保护原理Hook函数Openprocess
Win32子系统: ...