MEF无法检测到插件依赖关系。

13

我在使用MEF和一个插件文件夹时遇到了问题。

我的主要应用程序支持通过MEF使用插件。主应用程序不引用包含.NET任务类型的程序集以进行多线程处理,但其中一个或多个插件会引用它们。

插件位于Plugins文件夹中,我正在使用DirectoryCatalog。

我一直收到由MEF抛出的ReflectionTypeLoadException。

无法加载一个或多个请求的类型。检索LoaderExceptions属性以获取更多信息。

LoaderExceptions属性包含FileNotFoundException。

“无法加载文件或程序集'System.Threading.Tasks,Version=1.5.11.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a'或其某个依赖项。系统找不到指定的文件。”:“System.Threading.Tasks,Version=1.5.11.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a”

该插件通过Microsoft NuGet包引用引用System.Threading.Tasks。

这是我的辅助方法:

public static void Compose(IEnumerable<string> searchFolders, params object[] parts)
{
    // setup composition container
    var catalog = new AggregateCatalog();

    // check if folders were specified
    if (searchFolders != null)
    {
        // add search folders
        foreach (var folder in searchFolders.Where(System.IO.Directory.Exists))
        {
            catalog.Catalogs.Add(new DirectoryCatalog(folder, "*.dll"));
        }
    }

    catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));

    // compose and create plug ins
    var composer = new CompositionContainer(catalog);
    composer.ComposeParts(parts);
}

public class MEFComposer
{
    [ImportMany(typeof(IRepository))]
    public List<IRepository> Repositories;

    [ImportMany(typeof(ILogging))]
    public List<ILogging> LoggingRepositories;

    [ImportMany(typeof(IPlugin))]
    public List<IPlugin> Plugins;
}

这是我正在使用的代码来调用MEF并加载插件。

public void Compose()
{
    // try to connect with MEF types
    try
    {
        var parts = new MEFComposer();
        MEFHelpers.Compose(new[] { Path.Combine(Application.StartupPath, "Plugins") }, parts);
        RepositoryFactory.Instance.Repository = parts.Repositories.FirstOrDefault();
        Logging.Repositories.AddRange(parts.LoggingRepositories);
        foreach (var plugin in parts.Plugins)
        {
            this.applicationApi.Plugins.Add(plugin);
            plugin.Connect(this.applicationApi);
        }
    }
    catch
    {
        // ERR: handle error
    }
}

尽管Microsoft.Threading.Tasks.dll及其相关程序集文件存在于插件文件夹中,但主应用程序的二进制文件夹中没有,为什么MEF无法加载这些插件?是否有办法告诉MEF在Plugins文件夹中搜索程序集依赖项?

使用插件模型意味着我无法预测插件可能引用哪些程序集,因此我无法将它们包含在应用程序的主二进制文件夹中,这就是为什么我希望所有相关的插件和插件依赖项都在plugins文件夹中的原因。


2
你解决了这个问题吗? - Gus
我也对解决方案或指向正确方向的提示感兴趣,因为我目前面临着相似的问题。 - Paul Weiland
我不记得为什么(我的意思是可能是因为这个原因),但在我的代码中,我总是将我的“bin”文件夹添加为“DirectoryCatalog”的文件夹之一。这并不一定指向问题,但你试过了吗? - Sergey Zakharchenko
1个回答

1
您在支持第三方插件时遇到了一个重要问题。您的问题是,当您加载插件时,运行时只会在您的AppDomain所知道的指定文件夹中需要时搜索其引用。那将是该进程的工作目录,然后是路径等
实质上,您正在加载需要System.Threading.Tasks的插件。该DLL位于您的/Plugin文件夹中。当.net加载您的插件时,它将搜索该程序集,但无法找到它,因为它位于/Plugin文件夹中并失败了。
有几种解决方法。
  1. 不要使用插件文件夹
这将是最简单的解决方案,当所有程序集(包括第三方库的引用)都位于您的WorkingDirectory中时,.net将没有任何问题找到该插件的所有引用。
  1. 将插件文件夹添加到探测路径中

这将扩展 .NET 搜索第三方参考的路径:

https://learn.microsoft.com/en-us/dotnet/framework/deployment/how-the-runtime-locates-assemblies#locating-the-assembly-through-probing

  1. 为每个插件使用 AppDomains。

在这里我选择使用 AppDomain,因为它不仅允许您将程序集加载到自己的“容器”中,而且还可以模拟作为插件的工作目录。如果其中一个插件例如使用与您的应用程序相同但不同版本的框架,那么这会很方便。

  1. 自己加载依赖项

这将是解决此问题的“直接”方法。您可以将每个程序集作为 ReflectionOnly 加载,确定所有依赖项,然后加载这些依赖项。这几乎保证可以正常工作。

4.1. AssemblyResolve 事件

这是另一种将.net重定向以从PluginFolder加载程序集的方法。 https://learn.microsoft.com/en-us/dotnet/api/system.appdomain.assemblyresolve?view=netframework-4.8 编辑: AssemblyCatalog也存在某些问题,它使用Assembly.Load而不是Assembly.LoadFrom来加载给定的程序集。这是问题的一个重要部分,因为LoadFrom会探测程序集源路径的依赖项,而Load则不会。

https://github.com/JPVenson/MSEF/blob/master/JPB.Shell/JPB.Shell.MEF/Model/StrongNameCatalog.cs

您可以使用像这样使用LoadFrom的目录。 免责声明:我是该项目的创建者。

你有解决方案3的示例或链接吗?我在一个基于MEF的插件应用程序中遇到了同样的问题(找不到引用的程序集),我想尝试将每个插件加载到单独的应用程序域中,但不知道如何操作。 - sawie
@sawie 你使用的是哪个DotNet版本?Framework、Core、Standard还是Net5+? - Venson
我正在使用Net6,刚刚读到AppDomains在那里不受支持,应该使用AssemblyLoadContext代替?但我无法找到如何控制MEF上程序集(及其依赖项)加载的线索。 - sawie
@sawie 是的,没错。有很多非常好的例子,比如这个 https://siderite.dev/blog/dynamically-loading-types-from-assembly.html/ - Venson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接