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

时间:2016-06-18 09:13:12

标签: c# .net .net-core .net-core-rc2

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

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

[Required]
    public IList<ListProductViewModel> Products { get; set; }

 @for (int i = 0; i < Model.Products.Count(); i++)
  {
  <tr id=@Model.Products[i].Id>
    <td>@(rowNo++).</td>
    <td>@Html.DisplayFor(modelItem => Model.Products[i].Name)</td>
    <td>@Html.DisplayFor(modelItem => Model.Products[i].Description)</td>
    <td>@Html.DisplayFor(modelItem => Model.Products[i].Price)</td>
    <td>@Html.DisplayFor(modelItem => Model.Products[i].Tax)</td>
    <td>@Html.DisplayFor(modelItem => Model.Products[i].MeasureUnit)</td>
    <td>@Html.DisplayFor(modelItem => Model.Products[i].Currency)</td>
    <td>@Html.NumberTextBoxFor(modelItem => Model.Products[i].Quantity)</td>
    <td>@Html.CheckBoxBoxFor(modelItem => Model.Products[i].IsSelected)</td>
  </tr>
  }

因此,dll文件的名称为namespace MyClassLib.SampleClasses { public class Sample { public string SayHello(string name) { return $"Hello {name}"; } public DateTime SayDateTime() { return DateTime.Now; } } } ,其位于MyClassLib.dll

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

Sample

注意: 通过.Net Core我的意思是RC2版本。

8 个答案:

答案 0 :(得分:33)

目前针对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"
    }
  }
}

答案 1 :(得分:17)

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

仅在.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(看看问题)然后我的应用程序调用方法{ {1}}并将“John Doe”作为名称传递给它,因此程序会打印:

SayHello()

快速说明: 方法"Hello John Doe"的覆盖无关紧要,因此您也可以将其内容替换为Load,并且不应影响我们关心的任何内容。

答案 2 :(得分:10)

感谢您的分享。它也在使用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($"{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);
    }
}

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

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

答案 3 :(得分: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);

答案 4 :(得分:1)

@Rob,我能让你的例子构建的唯一方法是将你的“myInstance”类型更改为动态

将类型保留为 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等Doh!

答案 5 :(得分:1)

我认为下面的内容对您有用,希望这对像我这样的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);

答案 6 :(得分:0)

我对此进行了深入研究,我尝试了DependencyContext方法...虽然效果很好,但它有一些局限性,它不同于启动应用程序的c ++ dotnet应用程序中的标准程序集分辨率。您必须手动进行名称匹配,并且如果您的主机应用程序是已发布的主机应用程序,则您将没有nuget文件夹的探测路径,如果子程序集处于调试状态并且使用nuget,则该路径是个问题(可解决)... < / p>

因此,这是另一种解决方案:如果手动加载程序集(assemblyB)的应用程序(assemblyA)没有依赖关系(或与AssemblyB没有冲突的依赖关系),我建议作弊并默认为AssemblyB的程序集解析度。 dotnet.exe有一个隐藏的宝石,使您可以加载所选的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 :(得分:0)

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

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