我相信我已经用尽了与该主题相关的答案和评论。 我用来演示该问题的项目的完整源代码可以在这里找到: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);
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 上运行它。
任何帮助将不胜感激!
我也尝试过使用
AssemblyDependencyResolver
(示例来自https://tsuyoshiushio.medium.com/understand-advanced-assembleloadcontext-with-c-16a9d0cfeae3和https://gist.github.com/TsuyoshiUshio/ 551cb0f71c704aca75209552af50fc7a#file-pluginloadcontext-cs),它可以工作,但是 Runner
、Module1
和 Module2
中的所有三个都以某种方式加载相同版本的 RestWrapper
依赖项,而不是打包在每个输出目录中的版本.
要求将代码粘贴到此处。
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;
}
}
}
}
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");
}
}
}
}
}
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");
}
}
}
}
}
我认为问题在于您正在使用 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;
};