如何在 .NET Core 控制台应用程序中加载位于文件夹中的程序集

Posted

技术标签:

【中文标题】如何在 .NET Core 控制台应用程序中加载位于文件夹中的程序集【英文标题】:How to load assemblies located in a folder in .NET Core console app 【发布时间】:2016-10-20 02:25:26 【问题描述】:

我正在 .NET Core 平台上制作控制台应用程序,我想知道,如何使用 C# 动态功能加载程序集(.dll 文件)和实例化类?它似乎与 .NET 4.X 大不相同,并且没有真正记录在案......

例如,假设我有一个类库 (.NET Core),它只有一个类:

namespace MyClassLib.SampleClasses

    public class Sample
    
        public string SayHello(string name)
        
            return $"Hello name";
        

        public DateTime SayDateTime()
        
            return DateTime.Now;
        
    

所以 dll 文件的名称是 MyClassLib.dll,它位于 /dlls/MyClassLib.dll

现在我想在一个简单的控制台应用程序 (.NET Core) 中加载它并实例化 Sample 类并在以下控制台应用程序中使用 C# 的动态功能调用方法:

namespace AssemblyLoadingDynamic

    public class Program
    
        public static void Main(string[] args)
        
            // load the assembly and use the classes
        
    

注意: .NET Core 指的是 RC2 版本。

【问题讨论】:

【参考方案1】:

目前针对netcoreapp1.0 运行,您实际上不需要去实现自己的AssemblyLoader。存在一个Default,它工作得很好。 (因此@VSG24 提到Load 没有做任何事情)。

using System;
using System.Runtime.Loader;

namespace AssemblyLoadingDynamic

    public class Program
    
        public static void Main(string[] args)
        
            var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\MyDirectory\bin\Custom.Thing.dll");
            var myType = myAssembly.GetType("Custom.Thing.SampleClass");
            var myInstance = Activator.CreateInstance(myType);
        
       

project.json 看起来像:


  "version": "1.0.0-*",
  "buildOptions": 
    "emitEntryPoint": true
  ,

  "dependencies": 
    "Microsoft.NETCore.App": 
      "type": "platform",
      "version": "1.0.1"
    ,
    "System.Runtime.Loader": "4.0.0"
  ,

  "frameworks": 
    "netcoreapp1.0": 
      "imports": "dnxcore50"
    
  

【讨论】:

这比以前的答案好多了。谢谢! 不加载 .NET Core 程序集,令人惊讶的是它只加载了普通的 .NET Framework 程序集!!!【参考方案2】:

不确定这是否是最好的方法,但这是我想出的:

仅在 .Net Core RC2 - Windows 上测试

using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;

namespace AssemblyLoadingDynamic

    public class Program
    
        public static void Main(string[] args)
        
            var asl = new AssemblyLoader();
            var asm = asl.LoadFromAssemblyPath(@"C:\Location\Of\" + "SampleClassLib.dll");

            var type = asm.GetType("MyClassLib.SampleClasses.Sample");
            dynamic obj = Activator.CreateInstance(type);
            Console.WriteLine(obj.SayHello("John Doe"));
        

        public class AssemblyLoader : AssemblyLoadContext
        
            // Not exactly sure about this
            protected override Assembly Load(AssemblyName assemblyName)
            
                var deps = DependencyContext.Default;
                var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
                var assembly = Assembly.Load(new AssemblyName(res.First().Name));
                return assembly;
            
        
    

MyClassLib.SampleClasses 是命名空间,Sample 是类型,也就是类名。

执行时,它会尝试在内存中加载SampleClassLib.dll 编译的类库,并让我的控制台应用程序访问MyClassLib.SampleClasses.Sample(看看问题)然后我的应用程序调用方法SayHello() 并通过“John Doe”作为它的名字,因此程序打印:

"Hello John Doe"

快速说明: Load 方法的覆盖无关紧要,因此您不妨将其内容替换为 throw new NotImplementedException(),它应该不会影响我们关心的任何内容。

【讨论】:

AssemblyLoadContext在asp.net core 1.0中不可用,只能在System.Runtime.Loader 4.0.0-beta-23516中找到 @DilyanDimitrov 我在 1.0 版上使用上面的代码没有问题。 在哪个包中可以找到这个AssemblyLoadContext类,我的VS建议添加我上面提到的包 @DilyanDimitrov 这是整个抽象类gist.github.com/VSG24/9fe807a1f96073e2d4b2705a117e7439 如我所说,如果你只是想加载一些程序集,只需在Load 方法中返回 null【参考方案3】:

感谢您的分享。它也适用于 Net Core 1.0。如果您的程序集在同一路径下需要另一个程序集,您可以使用下面的代码示例。

using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
public class AssemblyLoader : AssemblyLoadContext

    private string folderPath;

    public AssemblyLoader(string folderPath)
    
        this.folderPath = folderPath;
    

    protected override Assembly Load(AssemblyName assemblyName)
    
        var deps = DependencyContext.Default;
        var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
        if (res.Count > 0)
        
            return Assembly.Load(new AssemblyName(res.First().Name));
        
        else
        
            var apiApplicationFileInfo = new FileInfo($"folderPathPath.DirectorySeparatorCharassemblyName.Name.dll");
            if (File.Exists(apiApplicationFileInfo.FullName))
            
                var asl = new AssemblyLoader(apiApplicationFileInfo.DirectoryName);
                return asl.LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
            
        
        return Assembly.Load(assemblyName);
    

请记住将以下依赖项添加到您的 project.json 文件中:

 "System.Runtime.Loader"
 "Microsoft.Extensions.DependencyModel"

【讨论】:

帮了我大忙 如果它需要另一个位于 .NET 包目录中的程序集? 这适用于加载程序集,但它似乎并不完全正确,检查新加载的类型中的底层类型的 IsSubclassOf 对于已知的继承类型返回 false ..【参考方案4】:

使用 .NET Core 1.1 / Standard 1.6,我发现 AssemblyLoader 不可用,AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath) 给了我一个“无法加载文件或程序集 xxx”的错误。

最后,下面的这个解决方案对我有用 - 纯粹是通过添加一个步骤来获取 AssemblyName 对象:

var assemblyName = AssemblyLoadContext.GetAssemblyName(assemblyPath);
var assembly = Assembly.Load(assemblyName);

【讨论】:

谢谢!不幸的是,我也有AssemblyLoadContext.Default.Resolving 也引发了异常。 第一个解决方案现在适用于 .NET Core 2.x 及更高版本。【参考方案5】:

@Rob,我可以构建您的示例的唯一方法是将您的“myInstance”类型更改为 dynamic

将类型保留为 var 确实允许构建代码,但是一旦我尝试使用运行时加载的程序集中的方法,就会出现编译器错误,例如 myInstance 不包含方法 X。我是新来的,但将类型标记为动态,似乎是有道理的。如果该类型是在运行时加载的,那么编译器如何验证 myInstance 将包含方法 X 或 prop Y ?通过将 myInstance 键入为动态的,我相信您正在删除编译器检查,因此我可以让示例构建和运行得很好。不确定这是 100% 正确的方法(我不太了解,您可能会建议使用动态存在问题?)但这是我让它工作的唯一方法,而不必费心创建自己的AssemblyLoader(正如您正确指出的那样)。

所以...

using System;
using System.Runtime.Loader;

namespace TestApp

    class Program
    
        static void Main(string[] args)
        
            var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\Documents\Visual Studio 2017\Projects\Foo\Foo\bin\Debug\netcoreapp2.0\Foo.dll");
            var myType = myAssembly.GetType("Foo.FooClass");
            dynamic myInstance = Activator.CreateInstance(myType);
            myInstance.UpperName("test");
        
    

希望这对新手有所帮助,我花了很长时间才查明为什么 myInstance 作为 var 没有方法 X 等等!

【讨论】:

这是正确的。在处理动态类型时,类型推断通常不起作用。【参考方案6】:

我对此进行了深入研究,我尝试了 DependencyContext 方法...它运行良好,但有一些限制,并且它不同于启动您的应用程序的 c++ dotnet 应用程序中的标准程序集分辨率。您必须手动进行名称匹配,如果您的主机应用程序是已发布的应用程序,您将没有 nuget 文件夹的探测路径,如果您的子程序集处于调试状态并使用 nuget...

所以这是另一个解决方案:如果应用程序 (assemblyA) 手动加载程序集 (assemblyB) 没有依赖项(或与程序集 B 没有冲突的依赖项),我建议作弊并默认使用程序集 B 的程序集解析。 dotnet.exe 有一个隐藏的 gem,可让您加载您选择的 deps 文件,以便您可以执行以下操作:

dotnet exec --depsfile pathToAssemblyB\assemblyB.deps.json --runtimeconfig pathToAssemblyB\assemblyB.runtimeconfig.json AssemblyA.dll

然后您可以按照其他答案中的说明加载程序集

var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath("pathToAssemblyB\\AssemblyB.dll");

这样,它会正确解析 assemblyB 的所有依赖关系,但不会正确解析 assemblyA 的所有依赖项。这是一个相反的问题,但如果你有一个小应用程序想在一个大应用程序中进行远程处理,它很有用。另一个问题是您需要知道您将在启动应用程序时使用 assemblyB 并且每次执行它只工作一次。所以有一组不同的问题,你可以根据你的情况选择你的方法。请注意,它是一个不受支持/未记录的功能,但它用于 EF 核心工具,所以它现在是“可行的”......

【讨论】:

【参考方案7】:

我认为下面的内容对你有用,希望对像我这样的 MEF2 新手有所帮助。

    /// <summary>
    /// Gets the assemblies that belong to the application .exe subfolder.
    /// </summary>
    /// <returns>A list of assemblies.</returns>
    private static IEnumerable<Assembly> GetAssemblies()
    
        string executableLocation = AppContext.BaseDirectory;
        string directoryToSearch = Path.Combine(Path.GetDirectoryName(executableLocation), "Plugins");
        foreach (string file in Directory.EnumerateFiles(directoryToSearch, "*.dll"))
        
            Assembly assembly = null;
            try
            
                //Load assembly using byte array
                byte[] rawAssembly = File.ReadAllBytes(file);
                assembly = Assembly.Load(rawAssembly);
            
            catch (Exception)
            
            

            if (assembly != null)
            
                yield return assembly;
            
        
    

另一个,但在 .netstandard1.3 中,两者都不可用。

var assembiles = Directory.GetFiles(Assembly.GetEntryAssembly().Location, "*.dll", SearchOption.TopDirectoryOnly)
        .Select(AssemblyLoadContext.Default.LoadFromAssemblyPath);

【讨论】:

【参考方案8】:

我一直在使用以下代码来加载程序集,并从加载的程序集中调用类中的方法。

    private static FormCustomized loadLayout(global::System.String layoutFilename, global::System.String layoutNameSpace)
    
        FormCustomized mainForm = default;
        Type typeMainLayout = default;
        FileInfo layoutFile;
        layoutFile = new FileInfo(layoutFilename);
        layoutFile.Refresh();
        if (!layoutFile.Exists)
        
            MessageBox.Show("Layout file not found. You need to reinstall the program");
            return default;
        

        try
        
            Assembly assemblyRaw = Assembly.LoadFrom(layoutFilename);
            AssemblyLoadContext context = AssemblyLoadContext.Default;
            Assembly assembly = context.LoadFromAssemblyPath(layoutFilename);


            Type typeMainLayoutIni = assembly.GetType(layoutNameSpace + ".initializeLayoutClass");
            Object iniClass = Activator.CreateInstance(typeMainLayoutIni, true);
            MethodInfo methodInfo = typeMainLayoutIni.GetMethod("AssembliesToLoadAtStart");
            enVars.assemblies = (Dictionary<string, Environment.environmentAssembliesClass>)methodInfo.Invoke(iniClass, default);
            typeMainLayout = assembly.GetType(layoutNameSpace + ".mainAppLayoutForm");
            mainForm = Activator.CreateInstance(typeMainLayout, enVars) as FormCustomized;
        
        catch (Exception ex)
        
            return default;
        

        return default;
    

【讨论】:

以上是关于如何在 .NET Core 控制台应用程序中加载位于文件夹中的程序集的主要内容,如果未能解决你的问题,请参考以下文章

在 Asp.Net Core 中加载 XSLT

在 ASP.NET Core 应用程序中加载自定义 ConfigSection

在 ASP.NET Core PartialView 中加载脚本后渲染脚本部分

在 Core Data 中加载 csv 文件

如何在 WPF .NET Core 应用程序中启用控制台?

Bootstrap5 和其他自定义 CSS 未在 .net core web api 中加载