当程序集和调用应用程序具有相同依赖项的不同版本时,AssemblyLoadContext 和依赖项解析

问题描述 投票:0回答:1

我相信我已经用尽了与该主题相关的答案和评论。 我用来演示该问题的项目的完整源代码可以在这里找到:https://github.com/jchristn/DependencyExample

假设有一个包含三个项目的解决方案。 第一个是依赖 RestWrapper v3.0.19 的控制台应用程序 (

Runner
)。 第二个是依赖 RestWrapper v3.0.20 的类库 (
Module1
)。 第三个是类库(
Module2
),依赖于 RestWrapper v3.0.18.

所有这三个都尝试对在

Runner
中检索到的用户指定的 URL 执行 HTTP GET。
Runner
然后使用
AssemblyLoadContext
LoadFromAssemblyPath
Resolving
Activator.CreateInstance
创建并调用
Process
中的
Runner1
方法,然后使用
Runner2
,执行相同的 HTTP GET,例如

实例化模块

AssemblyLoadContext ctx1 = new AssemblyLoadContext("module1", false);
Assembly asm1 = ctx1.LoadFromAssemblyPath(Path.GetFullPath("module1/Module1.dll"));

ctx1.Resolving += (ctx, assemblyName) =>
{
    Console.WriteLine("| Module 1 loading assembly " + assemblyName.FullName);
    var parts = assemblyName.FullName.Split(',');
    string name = parts[0];
    var version = Version.Parse(parts[1].Split('=')[1]);
    string filename = new FileInfo(Path.GetFullPath("module1/" + name + ".dll")).FullName;
    Console.WriteLine("| Module 1 loading from file " + filename);

    Assembly asm = Assembly.LoadFrom(filename);
    Console.WriteLine("| Module 1 loaded assembly " + filename);
    return asm;
};

Type type1 = asm1.GetType("Module1.Processor", true);
dynamic instance1 = Activator.CreateInstance(type1);
instance1.Process(url);

简单的 RESTful 请求

using (RestRequest req = new RestRequest(url))
{
    using (RestResponse resp = req.Send())
    {
        Console.WriteLine("| Status (runner) : " + resp.StatusCode + " " + resp.ContentLength + " bytes");
    }
}

我可以确认这些文件确实存在:

C:\Code\Misc\DependencyExample\Runner\bin\Debug\net8.0>dir module1
 Volume in drive C is OS
 Volume Serial Number is 541C-D54E

 Directory of C:\Code\Misc\DependencyExample\Runner\bin\Debug\net8.0\module1

05/24/2024  08:22 AM    <DIR>          .
05/24/2024  08:22 AM    <DIR>          ..
05/24/2024  08:17 AM            37,648 Module1.deps.json
05/24/2024  08:17 AM             4,608 Module1.dll
05/24/2024  08:17 AM            10,548 Module1.pdb
05/20/2024  10:26 PM            29,184 RestWrapper.dll
05/20/2024  10:12 PM            11,776 Timestamps.dll
               5 File(s)         93,764 bytes
               2 Dir(s)  51,069,906,944 bytes free

C:\Code\Misc\DependencyExample\Runner\bin\Debug\net8.0>dir module2
 Volume in drive C is OS
 Volume Serial Number is 541C-D54E

 Directory of C:\Code\Misc\DependencyExample\Runner\bin\Debug\net8.0\module2

05/24/2024  08:22 AM    <DIR>          .
05/24/2024  08:22 AM    <DIR>          ..
05/24/2024  08:17 AM            37,648 Module2.deps.json
05/24/2024  08:17 AM             4,608 Module2.dll
05/24/2024  08:17 AM            10,548 Module2.pdb
05/20/2024  10:26 PM            29,184 RestWrapper.dll
05/20/2024  10:12 PM            11,776 Timestamps.dll
               5 File(s)         93,764 bytes
               2 Dir(s)  51,069,906,944 bytes free

Module1
Module2
在其
.csproj
中都有条目,用于将依赖文件复制到输出目录中:

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>

我的操作假设是,通过将它们加载到具有自己的

AssemblyLoadContext
功能的单独
Resolving
中,他们将能够加载这些不同的版本,但在尝试运行时会失败并出现
System.IO.FileLoadException: Could not load file or assembly 'RestWrapper, Version=3.0.20.0, Culture=neutral, PublicKeyToken=null'. Could not find or load a specific file. (0x80131621)
异常
Runner1

控制台输出

C:\Code\Misc\DependencyExample\Runner\bin\Debug\net8.0>runner

URL: https://www.google.com
| Status (runner) : 200 57841 bytes
| Module 1 loading assembly RestWrapper, Version=3.0.20.0, Culture=neutral, PublicKeyToken=null
| Module 1 loading from file C:\Code\Misc\DependencyExample\Runner\bin\Debug\net8.0\module1\RestWrapper.dll
System.IO.FileLoadException: Could not load file or assembly 'RestWrapper, Version=3.0.20.0, Culture=neutral, PublicKeyToken=null'. Could not find or load a specific file. (0x80131621)
File name: 'RestWrapper, Version=3.0.20.0, Culture=neutral, PublicKeyToken=null'
 ---> System.IO.FileLoadException: Could not load file or assembly 'RestWrapper, Version=3.0.20.0, Culture=neutral, PublicKeyToken=null'.
   at System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath(String assemblyPath)
   at System.Reflection.Assembly.LoadFrom(String assemblyFile)
   at DependencyExample.Program.<>c.<Main>b__0_0(AssemblyLoadContext ctx, AssemblyName assemblyName) in C:\Code\Misc\DependencyExample\Runner\Program.cs:line 49
   at System.Runtime.Loader.AssemblyLoadContext.GetFirstResolvedAssemblyFromResolvingEvent(AssemblyName assemblyName)
   at System.Runtime.Loader.AssemblyLoadContext.ResolveUsingEvent(AssemblyName assemblyName)
   at System.Runtime.Loader.AssemblyLoadContext.ResolveUsingResolvingEvent(IntPtr gchManagedAssemblyLoadContext, AssemblyName assemblyName)
   at Module1.Processor.Process(String url)
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
   at DependencyExample.Program.Main(String[] args) in C:\Code\Misc\DependencyExample\Runner\Program.cs:line 56

我已经在上面的链接上传了完整的源代码(故意省略了

.gitignore
)。
bin/Debug/net8.0
Module1
Module2
输出被复制到
module1/
module2/
目录中的子目录
Runner
bin/Debug/net8.0
中。

此外,问题的另一个问题是我需要在 Ubuntu 和 Windows 上运行它。

任何帮助将不胜感激!

编辑1

我也尝试过使用

AssemblyDependencyResolver
(示例来自https://tsuyoshiushio.medium.com/understand-advanced-assembleloadcontext-with-c-16a9d0cfeae3https://gist.github.com/TsuyoshiUshio/ 551cb0f71c704aca75209552af50fc7a#file-pluginloadcontext-cs),它可以工作,但是
Runner
Module1
Module2
中的所有三个都以某种方式加载相同版本的
RestWrapper
依赖项,而不是打包在每个输出目录中的版本.

编辑2

要求将代码粘贴到此处。

Runner 中的 Program.cs

namespace DependencyExample
{
    using RestWrapper;
    using System;
    using System.IO;
    using System.Reflection;
    using System.Runtime.Loader;

    internal class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                Console.Write(Environment.NewLine + "URL: ");
                string url = Console.ReadLine();
                if (String.IsNullOrEmpty(url)) continue;

                try
                {
                    #region Local

                    using (RestRequest req = new RestRequest(url))
                    {
                        using (RestResponse resp = req.Send())
                        {
                            Console.WriteLine("| Status (runner) : " + resp.StatusCode + " " + resp.ContentLength + " bytes");
                        }
                    }

                    #endregion

                    #region Module1

                    PluginLoadContext ctx1 = new PluginLoadContext("module1/");
                    Assembly asm1 = ctx1.LoadFromAssemblyPath(Path.GetFullPath("module1/Module1.dll"));

                    ctx1.Resolving += (ctx, assemblyName) =>
                    {
                        Console.WriteLine("| Module 1 loading assembly " + assemblyName.FullName);
                        var parts = assemblyName.FullName.Split(',');
                        string name = parts[0];
                        var version = Version.Parse(parts[1].Split('=')[1]);
                        string filename = new FileInfo(Path.GetFullPath("module1/" + name + ".dll")).FullName;
                        Console.WriteLine("| Module 1 loading from file " + filename);

                        Assembly asm = Assembly.LoadFrom(filename);
                        Console.WriteLine("| Module 1 loaded assembly " + filename);
                        return asm;
                    };

                    Type type1 = asm1.GetType("Module1.Processor", true);
                    dynamic instance1 = Activator.CreateInstance(type1);
                    instance1.Process(url);

                    #endregion

                    #region Module2

                    PluginLoadContext ctx2 = new PluginLoadContext("module2/");
                    Assembly asm2 = ctx2.LoadFromAssemblyPath(Path.GetFullPath("module2/Module2.dll"));

                    ctx1.Resolving += (ctx, assemblyName) =>
                    {
                        Console.WriteLine("| Module 2 loading assembly " + assemblyName.FullName);
                        var parts = assemblyName.FullName.Split(',');
                        string name = parts[0];
                        var version = Version.Parse(parts[1].Split('=')[1]);
                        string filename = new FileInfo(Path.GetFullPath("module2/" + name + ".dll")).FullName;
                        Console.WriteLine("| Module 2 loading from file " + filename);

                        Assembly asm = Assembly.LoadFrom(filename);
                        Console.WriteLine("| Module 2 loaded assembly " + filename);
                        return asm;
                    };

                    Type type2 = asm2.GetType("Module2.Processor", true);
                    dynamic instance2 = Activator.CreateInstance(type2);
                    instance2.Process(url);

                    #endregion

                    Console.WriteLine("Finished URL " + url);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }
        }
        private class PluginLoadContext : AssemblyLoadContext
        {
            private AssemblyDependencyResolver _Resolver;

            public PluginLoadContext(string baseDirectory)
            {
                _Resolver = new AssemblyDependencyResolver(Path.GetFullPath(baseDirectory));
            }

            protected override Assembly? Load(AssemblyName name)
            {
                Console.WriteLine("  | Loading assembly: " + name);
                string path = _Resolver.ResolveAssemblyToPath(name);
                if (path != null)
                {
                    return LoadFromAssemblyPath(path);
                }
                return null;
            }

            protected override IntPtr LoadUnmanagedDll(string name)
            {
                Console.WriteLine("  | Loading unmanaged assembly: " + name);
                string path = _Resolver.ResolveUnmanagedDllToPath(name);
                if (path != null)
                {
                    return LoadUnmanagedDllFromPath(path);
                }
                return IntPtr.Zero;
            }
        }
    }
}

Module1 中的Processor.cs

namespace Module1
{
    using RestWrapper;
    using System;

    public class Processor
    {
        public Processor()
        {

        }

        public void Process(string url)
        {
            using (RestRequest req = new RestRequest(url))
            {
                using (RestResponse resp = req.Send())
                {
                    Console.WriteLine("| Status (module1): " + resp.StatusCode + " " + resp.ContentLength + " bytes");
                }
            }
        }
    }
}

Module2 中的Processor.cs

namespace Module2
{
    using RestWrapper;
    using System;

    public class Processor
    {
        public Processor()
        {

        }

        public void Process(string url)
        {
            using (RestRequest req = new RestRequest(url))
            {
                using (RestResponse resp = req.Send())
                {
                    Console.WriteLine("| Status (module2): " + resp.StatusCode + " " + resp.ContentLength + " bytes");
                }
            }
        }
    }
}
c# system.reflection activator
1个回答
0
投票

我认为问题在于您正在使用 Assembly.LoadFrom 加载依赖项(在 AssemblyLoadContext.Resolving 事件处理程序内)。由于其行为而失败。

我认为正确的方法是调用 AssemblyLoadContext.LoadFromAssemblyPath 方法。

ctx1.Resolving += (ctx, assemblyName) =>
{
    Console.WriteLine("| Module 1 loading assembly " + assemblyName.FullName);
    var parts = assemblyName.FullName.Split(',');
    string name = parts[0];
    var version = Version.Parse(parts[1].Split('=')[1]);
    string filename = new FileInfo(Path.GetFullPath("module1/" + name + ".dll")).FullName;
    Console.WriteLine("| Module 1 loading from file " + filename);

    //Assembly asm = Assembly.LoadFrom(filename); This line is replaced with the one below
    Assembly asm = ctx.LoadFromAssemblyPath(filename);

    Console.WriteLine("| Module 1 loaded assembly " + filename);
    return asm;
};

同样适用于

ctx2
。请注意,加载 Module2 时,您仍然使用
ctx1
而不是
ctx2
,因此这也需要修复。

AssemblyLoadContext ctx2 = new AssemblyLoadContext("module2", false);
Assembly asm2 = ctx2.LoadFromAssemblyPath(Path.GetFullPath("module2/Module2.dll")); // Replaced ctx1 with ctx2

ctx2.Resolving += (ctx, assemblyName) => // Replaced ctx1 with ctx2
{
    Console.WriteLine("| Module 2 loading assembly " + assemblyName.FullName);
    var parts = assemblyName.FullName.Split(',');
    string name = parts[0];
    var version = Version.Parse(parts[1].Split('=')[1]);
    string filename = new FileInfo(Path.GetFullPath("module2/" + name + ".dll")).FullName;
    Console.WriteLine("| Module 2 loading from file " + filename);

    //Assembly asm = Assembly.LoadFrom(filename); This line is replaced with the one below
    Assembly asm = ctx.LoadFromAssemblyPath(filename);

    Console.WriteLine("| Module 2 loaded assembly " + filename);
    return asm;
};
© www.soinside.com 2019 - 2024. All rights reserved.