.NET5启动器加载依赖Newtonsoft.Json的插件时Assembly.LoadFrom加载失败问题排查及优化方案咨询
为什么手动加载Newtonsoft.Json.dll会崩溃?
这里有几个核心原因导致了你的问题:
- 加载上下文隔离冲突:
Assembly.LoadFrom会把加载的程序集放到一个独立的加载上下文中,而你的totoPlugin.dll默认会从应用程序的默认加载上下文查找依赖。两者上下文不互通,即便你手动加载了Newtonsoft.Json,插件依然无法访问到这个已加载的程序集,最终引发加载失败甚至崩溃。 - 强名称版本不匹配:如果你从NuGet安装的Newtonsoft.Json是带强名称的(默认都是),插件依赖的版本、公钥标记必须和你手动加载的dll完全一致。哪怕版本号差一个小版本,CLR都会拒绝加载,且这类错误的提示往往很模糊,不会明确告诉你是版本或签名问题。
- CLR默认探测逻辑限制:当插件尝试解析依赖时,CLR只会去GAC、应用程序基目录、
PrivateBinPath这些默认路径查找。你手动LoadFrom的dll不在这些路径里,所以CLR在解析插件依赖时根本找不到它,反而可能因为重复尝试加载引发异常。
启动器加载带依赖插件的推荐实现方式
在.NET 5+中,官方推荐用**AssemblyLoadContext(ALC)**来管理插件加载,它能完美解决依赖隔离、路径解析的问题。下面给你几个靠谱的方案:
1. 自定义AssemblyLoadContext(最灵活)
创建专属的ALC来加载插件及其依赖,确保插件的依赖不会和启动器的依赖冲突,还能指定从插件目录自动加载依赖:
using System.Reflection; using System.Runtime.Loader; using System.IO; public class PluginLoadContext : AssemblyLoadContext { private readonly AssemblyDependencyResolver _resolver; public PluginLoadContext(string pluginPath) : base(isCollectible: true) { // 传入插件路径,让解析器自动识别依赖 _resolver = new AssemblyDependencyResolver(pluginPath); } protected override Assembly? Load(AssemblyName assemblyName) { // 优先从插件目录解析依赖 string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); if (!string.IsNullOrEmpty(assemblyPath)) { return LoadFromAssemblyPath(assemblyPath); } // 系统程序集交给默认上下文加载 return null; } protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { // 处理非托管依赖(如果插件有的话) string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); if (!string.IsNullOrEmpty(libraryPath)) { return LoadUnmanagedDllFromPath(libraryPath); } return IntPtr.Zero; } }
加载插件的代码示例:
// 插件所在目录 var pluginDir = Path.Combine(AppContext.BaseDirectory, "Plugins"); var pluginPath = Path.Combine(pluginDir, "totoPlugin.dll"); // 用自定义ALC加载插件 using var pluginLoadContext = new PluginLoadContext(pluginPath); var pluginAssembly = pluginLoadContext.LoadFromAssemblyPath(pluginPath); // 实例化插件并调用方法 var pluginType = pluginAssembly.GetType("TotoPlugin.Toto")!; var pluginInstance = Activator.CreateInstance(pluginType)!; // 假设插件有Run方法 ((dynamic)pluginInstance).Run();
这种方式下,ALC会自动帮你解析插件的所有依赖(包括Newtonsoft.Json),只要依赖和插件dll在同一个目录即可,完全不需要手动加载依赖。
2. 使用MEF(官方插件框架)
MEF是.NET官方的可扩展性框架,能自动发现、加载插件并处理依赖,适合插件化场景的快速开发。你需要安装System.Composition NuGet包:
- 首先在插件项目中定义并导出插件接口:
using System.Composition; // 公共插件接口(启动器和插件都要引用这个接口的项目/包) public interface IPlugin { void Run(); } [Export(typeof(IPlugin))] public class Toto : IPlugin { public void Run() { // 插件业务逻辑 } }
- 启动器中导入并加载插件:
using System.Composition.Hosting; using System.IO; using System.Reflection; // 配置容器,指定插件目录 var config = new ContainerConfiguration() .WithAssembly(Assembly.GetExecutingAssembly()) .WithAssembliesInPath(Path.Combine(AppContext.BaseDirectory, "Plugins")); using var container = config.CreateContainer(); // 获取所有实现IPlugin的插件 var plugins = container.GetExports<IPlugin>(); foreach (var plugin in plugins) { plugin.Run(); }
MEF会自动处理插件的依赖加载,只要依赖在插件目录或CLR可探测的路径中。
3. 关键注意事项
- 永远不要手动用
Assembly.LoadFrom加载插件的依赖,CLR的自动探测机制远比手动加载可靠,只要你配置对了加载路径或上下文。 - 如果多个插件需要共享同一个依赖版本,可以把共享依赖放到启动器目录,或者在自定义ALC中配置共享依赖的加载规则,避免重复加载。
内容的提问来源于stack exchange,提问作者Hiep
相关产品推荐
相关产品推荐

