让插件使用 .NET Core 3.1 中使用 MEF 的旧版本程序集

Have Plugin use older version of assembly using MEF in .NET Core 3.1

使用这篇文章 Using MEF in .NET Core 作为起点,我试图加载一个插件,该插件引用了我假装 "MessageQueue" 库的旧版本,该版本与主控制台应用程序使用的版本不同.

我的 "MessageQueue" 的版本 1.0.0 return 的值 "Message from v1.0"。 我的 "MessageQueue" return 的版本 1.1.0 的值 "Message from v1.1"。

我的主控制台应用程序引用 v1.1,而我的插件引用 v1.0。但是,当我调用该插件时,从它的 "MessageQueue" 编辑的消息 return 与控制台应用程序 "MessageQueue" 的消息 return 相同,即 "Message from v1.1"。

我希望插件将使用其 "MessageQueue" 和 return "Message from v1.0" 版本。

我的解决方案分为 4 个项目:

  1. ConsoleMEF - 主控制台应用程序
  2. 可插入 - class 包含 IMessageSender 接口的库
  3. MefPlugin - class 包含实现 IMessageSender 的 EmailSender 并标记为 [Exported] 的库。对 Messages
  4. v1.0 的硬汇编参考
  5. 消息 - class 包含 "MessageQueue" 的库(项目代码是 1.1 版,v1.0 的编译程序集已复制到插件路径)

插件代码在这里:

[Export(typeof(IMessageSender))] // Socket
public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        var messages = new MessageQueue();
        Console.WriteLine($"App Message Queue From Plugin: {messages.GetMessage()}");
        Console.WriteLine($"EMAIL: {message}");
    }
}

这是我加载插件的方式:

[Import] // Hook
public IMessageSender MessageSender
{
    get;
    set;
}

void Compose()
{
    // pluginPath contains:
    // - MefPlugin.dll
    // - Pluginable.dll
    // - Messages.dll, Version 1.0
    var pluginPath = Path.GetFullPath(@"c:\temp\plugins");

    var assemblies = Directory
        .GetFiles(pluginPath, "*.dll")
        .Select(AssemblyLoadContext.Default.LoadFromAssemblyPath)
        .ToList();

    var configuration = new ContainerConfiguration()
        .WithAssemblies(assemblies);

    using var container = configuration.CreateContainer();
    MessageSender = container.GetExport<IMessageSender>();
}

我是这样调用插件的:

void Run()
{

    Compose();
    var messages = new MessageQueue();
    // CORRECT: Prints "Message from v1.1"
    Console.WriteLine($"App Message Queue From Console: {messages.GetMessage()}"); 

    // MessageSender.Send also calls the following:
    //Console.WriteLine($"App Message Queue From Plugin: {messages.GetMessage()}"); 
    // WRONG: Prints "Message from v1.1"
    // EXPECTED: "Message from v1.0"
    MessageSender.Send("Hello MEF");
}

MessageQueue v1.0 具有以下代码:

public class MessageQueue
{
    public string GetMessage()
    {
        return "Message from v1.0";
    }
}

MessageQueue v1.1 具有以下代码:

public class MessageQueue
{
    public string GetMessage()
    {
        return "Message from v1.1";
    }
}

我过去曾使用 AppDomain 和 BasePath 轻松完成此操作。但我似乎无法弄清楚如何处理 AssemblyLoadContext。

我哪里错了?

解决方案似乎有两个方面。

  1. 创建一个新的 AssemblyLoadContext。
  2. 从插件目录中删除 Pluginable.dll。

Compose 现在看起来像这样:

void Compose()
{
    // pluginPath contains:
    // - MefPlugin.dll
    // - DELETED: Pluginable.dll.renamed
    // - Messages.dll, Version 1.0
    var pluginPath = Path.GetFullPath(@"c:\temp\plugins");

    //var loadContext = AssemblyLoadContext.Default;
    var loadContext = new AssemblyLoadContext("plugin"); // new context

    var assemblies = Directory
        .GetFiles(pluginPath, "*.dll")
        .Select(loadContext.LoadFromAssemblyPath)
        .ToList();

    var configuration = new ContainerConfiguration()
        .WithAssemblies(assemblies);

    using var container = configuration.CreateContainer();
    MessageSender = container.GetExport<IMessageSender>();
}

如果 Pluginable.dll 未从插件目录中删除,则会引发以下异常:

System.Composition.Hosting.CompositionFailedException: 'No export was found for the contract 'IMessageSender'.'

我认为它被引发是因为解析器将插件的 Pluginable.dll 视为独立于主应用程序的 Pluginable.dll。一旦它从插件目录中删除,我认为解析器现在必须被迫在别处寻找参考。