简体   繁体   English

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

[英]How to load assemblies located in a folder in .NET Core console app

I'm making a console app on the .NET Core platform and was wondering, how does one load assemblies (.dll files) and instantiate classes using C# dynamic features?我正在 .NET Core 平台上制作一个控制台应用程序,想知道如何使用 C# 动态功能加载程序集(.dll 文件)和实例化类? It seems so much different than .NET 4.X and it's not really documented...它似乎与 .NET 4.X 有很大不同,而且并没有真正记录下来......

For example, let's say I have a class library (.NET Core) and it has only one class:例如,假设我有一个类库 (.NET Core),它只有一个类:

namespace MyClassLib.SampleClasses
{
    public class Sample
    {
        public string SayHello(string name)
        {
            return $"Hello {name}";
        }

        public DateTime SayDateTime()
        {
            return DateTime.Now;
        }
    }
}

So the name of the dll file would be MyClassLib.dll and it's located in /dlls/MyClassLib.dll .因此 dll 文件的名称将是MyClassLib.dll并且它位于/dlls/MyClassLib.dll

Now I want to load this in a simple console app (.NET Core) and instantiate the Sample class and call the methods using dynamic features of C# in the following console app:现在我想在一个简单的控制台应用程序 (.NET Core) 中加载它并实例化Sample类并在以下控制台应用程序中使用 C# 的动态功能调用方法:

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

Note: By .NET Core, I mean the RC2 version.注意: .NET Core 是指 RC2 版本。

Currently running against netcoreapp1.0 you don't actually need to go to the extent of implementing your own AssemblyLoader .目前针对netcoreapp1.0运行,您实际上并不需要达到实现自己的AssemblyLoader的程度。 There is a Default that exists which works just fine.有一个存在的Default ,它工作得很好。 (Hence @VSG24 mentioning that the Load doesn't do anything). (因此@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);
        }
    }   
}

with project.json looking like: 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"
    }
  }
}

Not sure if it's the best way to do it but here's what I came up with:不确定这是否是最好的方法,但这是我想出的:

( Only tested on .Net Core RC2 - Windows ) 仅在 .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 is the namespace and Sample is the type aka class name. MyClassLib.SampleClasses是命名空间, Sample是类型,也就是类名。

When executed, it will try to load the SampleClassLib.dll compiled class library in the memory and gives my console app access to MyClassLib.SampleClasses.Sample (take a look at the question) then my app calls the method SayHello() and passes "John Doe" as name to it, Therefore the program prints:执行时,它会尝试在内存中加载SampleClassLib.dll编译的类库,并让我的控制台应用程序访问MyClassLib.SampleClasses.Sample (看看问题)然后我的应用程序调用方法SayHello()并传递“ John Doe”作为它的名字,因此程序打印:

"Hello John Doe"

Quick note: The override for the method Load doesn't matter so you might as well just replace its content with throw new NotImplementedException() and it shouldn't affect anything we care about.快速说明:方法Load的覆盖无关紧要,因此您不妨将其内容替换为throw new NotImplementedException()并且它不应该影响我们关心的任何事情。

Thanks for your sharing.感谢您的分享。 It is working with Net Core 1.0 also.它也适用于 Net Core 1.0。 If your assembly needs another assemblies at the same path, you can use the code sample below.如果您的程序集需要同一路径上的另一个程序集,您可以使用下面的代码示例。

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($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll");
            if (File.Exists(apiApplicationFileInfo.FullName))
            {
                var asl = new AssemblyLoader(apiApplicationFileInfo.DirectoryName);
                return asl.LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
            }
        }
        return Assembly.Load(assemblyName);
    }
}

Remember to add the following dependencies to your project.json file:请记住将以下依赖项添加到您的project.json文件中:

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

Using .NET Core 1.1 / Standard 1.6, I found that AssemblyLoader was not available, and AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath) gave me a "Could not load file or assembly xxx" error.使用 .NET Core 1.1 / Standard 1.6,我发现 AssemblyLoader 不可用,并且AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath)给了我一个“无法加载文件或程序集 xxx”的错误。

Finally, this solution below worked for me - purely by adding a step to get the AssemblyName object:最后,下面的这个解决方案对我有用 - 纯粹是通过添加一个步骤来获取 AssemblyName 对象:

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

@Rob, the only way I could get your example to build was to change your "myInstance" type to dynamic . @Rob,我可以让您构建示例的唯一方法是将您的“myInstance”类型更改为dynamic

Leaving the type as var does allow the code to build but as soon as I try and use a method from the runtime loaded assembly, I get compiler errors such as myInstance does not contain method X .将类型保留为var确实允许构建代码,但是一旦我尝试使用运行时加载的程序集中的方法,我就会收到编译器错误,例如myInstance 不包含方法 X I'm new at this but marking the type as dynamic, does seem to make sense.我是新手,但将类型标记为动态,似乎确实有意义。 If the type is loaded at runtime then how can the compiler verify myInstance will contain method X or prop Y ?如果类型是在运行时加载的,那么编译器如何验证 myInstance 将包含方法 X 或 prop Y ? By typing the myInstance as dynamic I believe you are removing the compiler checking and thus I could get the example to build and run just fine.通过将 myInstance 键入为动态,我相信您正在删除编译器检查,因此我可以使示例构建和运行得很好。 Not sure this is 100% the correct way ( I don't know enough and you may advise that there's an issue using dynamic?) but it is the only way I got it to work without having to go to the trouble of creating my own AssemblyLoader (as you correctly point out).不确定这是 100% 正确的方法(我知道的还不够多,你可能会建议使用 dynamic 有问题?)但这是我让它工作的唯一方法,而不必费心去创建我自己的AssemblyLoader(正如您正确指出的那样)。

So...所以...

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");
        }
    }
}

Hope this helps someone as being new it took me ages to pinpoint why myInstance as a var didn't have method X etc Doh!希望这对新人有所帮助,我花了很长时间才确定为什么 myInstance 作为var没有方法 X 等等!

I dig a lot into that, I tried the DependencyContext approach... It works well but it has some limitations and it is different from the standard assembly resolution that is in the the c++ dotnet app that starts your app.我对此进行了大量研究,我尝试了 DependencyContext 方法......它运行良好,但有一些限制,它与启动应用程序的 c++ dotnet 应用程序中的标准程序集分辨率不同。 You have to do name matching manually and if your host app is a published one, you won't have the probing path for the nuget folder which is a problem (solveable) if your child assembly is in debug and uses nuget...您必须手动进行名称匹配,并且如果您的主机应用程序是已发布的应用程序,则您将没有 nuget 文件夹的探测路径,如果您的子程序集处于调试状态并使用 nuget ,这是一个问题(可解决)...

So here is another solution: If the app (assemblyA) manually loading an assembly (assemblyB) has no dependencies (or no conflicting dependencies with assemblyB) I suggest cheating and defaulting to the assembly resolution of assemblyB.所以这是另一种解决方案:如果应用程序(assemblyA)手动加载程序集(assemblyB)没有依赖项(或与 assemblyB 没有冲突的依赖项),我建议作弊并默认为 assemblyB 的程序集解析。 There is an hidden gem for dotnet.exe that enable you to load the deps file of your choice so you can do something like this: dotnet.exe 有一个隐藏的 gem,它使您能够加载您选择的 deps 文件,以便您可以执行以下操作:

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

and then you can load the assembly as explained in other answers with然后您可以按照其他答案中的说明加载程序集

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

This way it will correctly resolve all dependencies for assemblyB but won't for assemblyA.这样,它将正确解析 assemblyB 的所有依赖项,但不会解析 assemblyA。 It is a reverse problem but if you have a small app that want to do some remoting in a big app, it is useful.这是一个相反的问题,但如果你有一个小应用程序想要在一个大应用程序中进行一些远程处理,它很有用。 Another problem is that you need to know that you are going to use assemblyB when starting your app and that it works only once per execution.另一个问题是,您需要知道在启动应用程序时将使用 assemblyB,并且每次执行时它只能工作一次。 So there is a different set of problems and you can choose your approach depending on your situation.因此,存在一系列不同的问题,您可以根据自己的情况选择方法。 Please note that it is an unsupported/undocumented feature but it is used in EF core tooling, so it is "viable" for now...请注意,这是一个不受支持/未记录的功能,但它用于 EF 核心工具,因此它现在是“可行的”...

I think this below will work for you, and hope this can help some MEF2 newbie like me.我认为下面的内容对您有用,希望这可以帮助像我这样的 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;
            }
        }
    }

another one, but in .netstandard1.3, neither was available.另一个,但在 .netstandard1.3 中,两者都不可用。

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

I've been using the following code for loading an assembly, and invoke a method inside a class from the loaded assembly.我一直在使用以下代码加载程序集,并从加载的程序集中调用类中的方法。

    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;
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在ASP.NET Core 2中动态加载程序集的安全性如何? - How secure is it to dynamically load assemblies in ASP.NET Core 2? 如何在 ASP.NET Core 1.0 RC2 中加载程序集 - How to load assemblies in ASP.NET Core 1.0 RC2 如何在不编译 .NET Core 的情况下加载程序集并调用它们的方法 - How to load assemblies and invoke their methods without compiling in .NET Core .net 核心控制台应用程序 - serilog 写入 bin/debug 文件夹 - .net core console app - serilog write to bin/debug folder 如何在dotnet核心中动态加载程序集 - How to dynamically load assemblies in dotnet core 为什么我的.NET App不加载位于添加到PATH环境变量的文件夹中的程序集? - Why my .NET App does not load an assembly located in a folder added to the PATH environment variable? 如何获取 .Net Core 控制台应用程序的 .exe 文件所在目录的路径? - How to get the path of the directory where the .exe file for a .Net Core console application is located? 如何使 .net 核心 3.1 控制台应用程序中的控制台记录器工作 - How to make console logger in .net core 3.1 console app work 如何识别核心.NET Framework程序集? - How to identify core .NET Framework assemblies? 如何在.NET Core中重定向探测程序集? - How to redirect probing for assemblies in .NET Core?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM