You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 10:12:37