替代Assembly.LoadFile、Assembly.LoadFrom和Assembly.Load的方法是什么?

3
我有两个问题,需要一个最小可复现示例,该示例有三个项目,目标是.NET Core 3.1,但我还想针对.NET Standard 2.0进行目标设置。
这个示例是一个需要在运行时加载程序集并使用提供的程序集的应用程序。
所加载的程序集引用了另一个项目,而该项目反过来使用了NuGet软件包。
代码
  • 项目Host是控制台应用程序,没有项目引用和软件包引用。它包含文件Program.cs:
class Program
{
    static void Main(string[] args)
    {
        var featurePath = System.IO.Path.GetFullPath(@"..\..\..\..\Feature\bin\Debug\netcoreapp3.1\Feature.dll");
        var featureAssembly = System.Reflection.Assembly.LoadFile(featurePath);
        var featureType = featureAssembly.GetType("SomeFeature");
        var featureInstance = System.Activator.CreateInstance(featureType);
        featureType.InvokeMember("PrintText",
            System.Reflection.BindingFlags.InvokeMethod, null, featureInstance, new object[0]);
    }
}
  • 项目功能是一个类库,它有一个指向..\Subfeature\Subfeature.csproj的项目引用,并包含文件SomeFeature.cs:
public class SomeFeature
{
    public void PrintText()
    {
        System.Console.WriteLine(new SomeSubfeature().GetText());
    }
}
  • 项目的子功能是一个类库,它具有对 Newtonsoft.Json 的 PackageReference,并包含文件 SomeSubfeature.cs:
public class SomeSubfeature
{
    public string GetText()
    {
        return Newtonsoft.Json.JsonConvert.SerializeObject("Some Text");
    }
}

已解决的问题

  • 第一个已解决的问题是因为所使用的程序集引用了另一个项目和/或使用了包。 Assembly.LoadFrom仅加载所请求的程序集,而未加载引用的项目程序集和包。这会导致FileNotFoundException,因为无法找到Subfeature。
     
    我通过以下方式解决此问题:(1)将Assembly.LoadFile(featurePath)替换为Assembly.LoadFrom(featurePath),以便在同一目录中也可以加载其他所需的DLL。 (2)通过添加到Feature.csproj中的代码<PropertyGroup><CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies></PropertyGroup>,使只在发布期间复制到相同目录的软件包DLL也在生成期间复制。

  • 第二个已解决的问题是当应用程序加载DLL时会锁定它们。这会阻止我在应用程序仍在运行时部署更新版本的DLL。当应用程序是IIS托管应用程序的一部分时,发布新版本变得棘手。.NET Core不再支持加载DLL的影子副本。
     
    我通过以下方式解决此问题:(1)将Assembly.LoadFile(featurePath)替换为Assembly.Load(System.IO.File.ReadAllBytes(featurePath)),以便在加载之前从读取的字节中加载所需的DLL。 (2)使用文件监视器在发生目录中DLL文件更改时重新加载应用程序。

最终问题

第一个问题的解决方案与第二个问题的解决方案不兼容。 Assembly.LoadFrom修复了我的第一个问题。 Assembly.Load修复我的第二个问题。但我还没有找到一个Assembly.LoadFile的替代方案,可以同时解决这两个问题。


1
https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/unloadability.md - Hans Passant
你到底想做什么?我已经读了你的帖子5遍,但没有得到任何关于你的任务和问题的信息。 - Dominic Jonas
1
@DominicJonas 给出了上面的应用程序示例,我想以 .net standard 2.0 的方式加载 Feature 程序集,并使其能够解析其依赖项(SubFeature 和 Newtonsoft),而不会保持已加载的程序集被锁定。 - nl-x
当一个dll被加载时,通常会以Share_Delete标志加载。这使您能够重命名锁定的dll。要替换正在运行的程序集,您只需要将它们重命名,然后复制新的程序集。您尝试过吗? - Alois Kraus
1个回答

2
AppDomain.CurrentDomain中添加AssemblyResolve事件处理程序,并像读取所有字节一样处理程序集加载。
        const string location = @"..\..\..\..\Dependency\bin\Debug\netcoreapp3.1\";
        static void Main(string[] args)
        {
            var domain = AppDomain.CurrentDomain;
            domain.AssemblyResolve += Domain_AssemblyResolve;
            var type = Type.GetType("Dependency.DependentClass,Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
            var obj = Activator.CreateInstance(type);
            Console.WriteLine(obj.ToString());     
        }

        private static Assembly Domain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            var dll = $"{args.Name.Split(",")[0]}.dll";
            var path = Path.Combine(location, dll);
            var asm = Assembly.Load(File.ReadAllBytes(path));
            return asm;
        }

一则小提示:如果不提供类型的全名,将会抛出错误。例如:"Dependency.DependentClass"

依赖程序集有两个依赖项,一个是 Newtonsoft.Json NuGet 包,另一个是另一个项目。 Bar 类位于另一个项目中。

 public class DependentClass
    {
        public int MyProperty { get; set; }

        public string AnotherProperty { get; set; }

        public override string ToString()
        {
            return JsonConvert.SerializeObject(new Bar());
        }
    }
编辑:在收到负评后,我再次查看了以上解决方案,虽然它可以工作,但更可能是一种.net Framework的解决方案,而不是.net Core的解决方案。因此,一个更.net Core的解决方案是使用AssemblyLoadContext

定义自定义程序集加载上下文。

public class CustomLoadContext : AssemblyLoadContext
    {
        private readonly AssemblyDependencyResolver resolver;

        public CustomLoadContext(string mainAssemblyToLoadPath) : base(isCollectible: true)
        {
            resolver = new AssemblyDependencyResolver(mainAssemblyToLoadPath);
        }


        protected override Assembly Load(AssemblyName name)
        {
            Console.WriteLine("Resolving : {0}",name.FullName);
            var assemblyPath = resolver.ResolveAssemblyToPath(name);
            if (assemblyPath != null)
            {
                return Assembly.Load(File.ReadAllBytes(assemblyPath));
            }

            return Assembly.Load(name);
        }
    }

加载您的程序集并让自定义加载器上下文加载它的依赖项。
        const string location = @"..\..\..\..\Dependency\bin\Debug\netcoreapp3.1\";

        static void Main(string[] args)
        {
            var fullPath = Path.GetFullPath(location + "Dependency.dll");
            var clx = new CustomLoadContext(fullPath); // initialize custom context
            var asm = clx.LoadFromStream(new MemoryStream(File.ReadAllBytes(fullPath))); // load your desired assembly
            var ins = asm.CreateInstance("Dependency.DependentClass");  
            Console.WriteLine(ins.ToString());
       
        }

所以我尝试了你的解决方案,但是resolver无法加载System.Runtime。assemblyPath变成了null,并抛出了一个System.IO.FileLoadException异常。"无法加载文件或程序集'System.Runtime, Version=4.2.2.0,..."。虽然我不确定为什么它要尝试解析和加载System.Runtime。 - nl-x
@nl-x 尝试返回 null 而不是 Assembly.Load(name)。如果您想要,我可以分享一个在我的本地环境中运行的 GitHub 存储库。 - Eldar
抱歉,我认为我没有正确实现if条件。我想现在它已经可以工作了。感谢我的同事,我已经朝着这个方向走了很远,甚至使用了AssemblyLoadContext。但是你使用AssemblyDependencyResolver的补充真是大开眼界。 - nl-x
@nl-x 很抱歉,目前还没有包含对 .NET Core 3.1 支持的 .NET Standard 版本。所以如果你想支持 .NET Standard,就只能使用 AppDomain 解决方案,尽管它看起来不太美观。 - Eldar
啊,你的意思是与<CopyLocalLockFileAssemblies>结合使用,这样所有的dll都会出现在输出目录中?因为对于Nugets来说,它们的位置需要被解析。<CopyLocalLockFileAssemblies>证明有点hacky。不过我今天还是会尝试一下。 - nl-x
显示剩余5条评论

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