簡體   English   中英

如何加載位於 .NET Core 控制台應用程序文件夾中的程序集

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

我正在 .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 版本。

目前針對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 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()並且它不應該影響我們關心的任何事情。

感謝您的分享。 它也適用於 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"

使用 .NET Core 1.1 / Standard 1.6,我發現 AssemblyLoader 不可用,並且AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath)給了我一個“無法加載文件或程序集 xxx”的錯誤。

最后,下面的這個解決方案對我有用 - 純粹是通過添加一個步驟來獲取 AssemblyName 對象:

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

@Rob,我可以讓您構建示例的唯一方法是將您的“myInstance”類型更改為dynamic

將類型保留為var確實允許構建代碼,但是一旦我嘗試使用運行時加載的程序集中的方法,我就會收到編譯器錯誤,例如myInstance 不包含方法 X 我是新手,但將類型標記為動態,似乎確實有意義。 如果類型是在運行時加載的,那么編譯器如何驗證 myInstance 將包含方法 X 或 prop Y ? 通過將 myInstance 鍵入為動態,我相信您正在刪除編譯器檢查,因此我可以使示例構建和運行得很好。 不確定這是 100% 正確的方法(我知道的還不夠多,你可能會建議使用 dynamic 有問題?)但這是我讓它工作的唯一方法,而不必費心去創建我自己的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 等等!

我對此進行了大量研究,我嘗試了 DependencyContext 方法......它運行良好,但有一些限制,它與啟動應用程序的 c++ dotnet 應用程序中的標准程序集分辨率不同。 您必須手動進行名稱匹配,並且如果您的主機應用程序是已發布的應用程序,則您將沒有 nuget 文件夾的探測路徑,如果您的子程序集處於調試狀態並使用 nuget ,這是一個問題(可解決)...

所以這是另一種解決方案:如果應用程序(assemblyA)手動加載程序集(assemblyB)沒有依賴項(或與 assemblyB 沒有沖突的依賴項),我建議作弊並默認為 assemblyB 的程序集解析。 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 核心工具,因此它現在是“可行的”...

我認為下面的內容對您有用,希望這可以幫助像我這樣的 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);

我一直在使用以下代碼加載程序集,並從加載的程序集中調用類中的方法。

    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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM