webentwicklung-frage-antwort-db.com.de

So laden Sie Baugruppen, die sich in einem Ordner in der .net Core Console-App befinden

Ich mache eine Konsolen-App auf der .Net Core-Plattform und habe mich gefragt, wie man Assemblies (.dll-Dateien) lädt und Klassen mit dynamischen C # -Funktionen instanziiert. Es scheint so viel anders als .Net 4.X zu sein und es ist nicht wirklich dokumentiert ...

Angenommen, ich habe eine Klassenbibliothek (.Net Core) und diese hat nur eine Klasse:

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

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

Der Name der DLL-Datei lautet also MyClassLib.dll und befindet sich in /dlls/MyClassLib.dll.

Jetzt möchte ich diese in eine einfache Konsolen-App (.Net Core) laden, die Sample-Klasse instanziieren und die Methoden mit dynamischen Features von C # in der folgenden Konsolen-App aufrufen:

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

Hinweis: Mit .Net Core meine ich RC2-Version.

26
Vahid Amiri

Ich bin mir nicht sicher, ob es der beste Weg ist, dies zu tun.

( Nur getestet auf .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 ist der Namespace und Sample ist der Typ, auch Klassenname genannt.

Wenn er ausgeführt wird, wird versucht, die SampleClassLib.dll-kompilierte Klassenbibliothek in den Speicher zu laden und meiner Konsolen-App Zugriff auf MyClassLib.SampleClasses.Sample zu geben (sehen Sie sich die Frage an). Dann ruft meine App die Methode SayHello() auf und übergibt "John Doe" als Namen dazu, deshalb druckt das Programm:

"Hello John Doe"

Kurzer Hinweis: Die Überschreibung für die Methode Load spielt keine Rolle. Sie können also den Inhalt einfach durch throw new NotImplementedException() ersetzen und sollte nichts beeinflussen, was uns interessiert.

17
Vahid Amiri

Wenn Sie derzeit gegen netcoreapp1.0 laufen, müssen Sie nicht wirklich so weit gehen, dass Sie Ihre eigene AssemblyLoader implementieren. Es gibt eine Default, die gut funktioniert. (Also @ VSG24 erwähnt, dass die Load nichts tut).

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

mit project.json sieht aus wie:

{
  "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"
    }
  }
}
33
Rob

Vielen Dank für Ihr Teilen. Es arbeitet auch mit Net Core 1.0. Wenn Ihre Assembly andere Assemblys im selben Pfad benötigt, können Sie das folgende Codebeispiel verwenden.

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

Denken Sie daran, Ihrer project.json-Datei die folgenden Abhängigkeiten hinzuzufügen:

 "System.Runtime.Loader"
 "Microsoft.Extensions.DependencyModel"
10
Uygar Manduz

Bei Verwendung von .net Core 1.1/Standard 1.6 stellte ich fest, dass AssemblyLoader nicht verfügbar war 

AssemblyLoadContext.Default.LoadFromAssemblyPath (assemblyPath)
gab mir einen Fehler "Datei konnte nicht geladen werden oder Assembly xxx". 

Schließlich hat diese Lösung für mich funktioniert - rein durch Hinzufügen eines Schrittes zum Abrufen des AssemblyName-Objekts. Hoffe, es hilft jedem, der stecken bleibt:

var assemblyName = AssemblyLoadContext.GetAssemblyName (assemblyPath); 
 var Assembly = Assembly.Load (assemblyName);
4
Pete M

@Rob, die einzige Möglichkeit, Ihr Beispiel zu erstellen, bestand darin, Ihren Typ "myInstance" in dynamic zu ändern. 

Wenn Sie den Typ als var belassen, kann der Code zwar erstellt werden, aber sobald ich versuche, eine Methode aus der Laufzeit-Assembly zu verwenden, erhalte ich Compiler-Fehler, z. B. enthält myInstance keine Methode X. Ich bin neu in diesem Bereich, aber den Typ als dynamisch zu markieren, scheint sinnvoll zu sein. Wenn der Typ zur Laufzeit geladen wird, wie kann dann der Compiler verifizieren, dass myInstance die Methode X oder prop Y enthält? Indem Sie myInstance als dynamisch eingeben, glaube ich, dass Sie die Compilerprüfung entfernen, und so könnte ich das Beispiel dazu bringen, einfach zu bauen und auszuführen. Ich bin mir nicht sicher, ob dies zu 100% der richtige Weg ist (ich weiß nicht genug und kann Ihnen sagen, dass es ein Problem mit der Dynamik gibt?), Aber es ist die einzige Möglichkeit, wie ich es schaffen kann, ohne mir die Mühe zu machen, mein eigenes zu erstellen AssemblyLoader (wie Sie richtig feststellen).

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

Ich hoffe, das hilft jemandem, der neu ist. Ich habe lange gebraucht, um herauszufinden, warum meine Instanz als var Methode X usw. nicht hatte. Doh!

1
Mortoman

Ich habe viel damit zu tun und den DependencyContext-Ansatz ausprobiert. Er funktioniert gut, weist jedoch einige Einschränkungen auf und unterscheidet sich von der standardmäßigen Assembly-Auflösung in der c ++ - Dotnet-App, mit der Ihre App gestartet wird. Sie müssen den Namensabgleich manuell durchführen. Wenn Ihre Host-App eine veröffentlichte App ist, haben Sie nicht den Prüfpfad für den Nuget-Ordner, der ein Problem darstellt (lösbar), wenn sich Ihre untergeordnete Assembly im Debugging befindet und Nuget verwendet.

Hier ist eine andere Lösung: Wenn die App (AssemblyA), die eine Assembly (AssemblyB) manuell lädt, keine Abhängigkeiten aufweist (oder keine widersprüchlichen Abhängigkeiten mit AssemblyB), schlage ich vor, die Assembly-Auflösung von AssemblyB zu betrügen und standardmäßig zu verwenden. Es gibt ein verstecktes Juwel für dotnet.exe, mit dem Sie die deps-Datei Ihrer Wahl laden können, damit Sie so etwas tun können:

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

und dann kannst du die Assembly wie in anderen Antworten mit erklärt laden

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

Auf diese Weise werden alle Abhängigkeiten für AssemblyB korrekt aufgelöst, nicht jedoch für AssemblyA. Es ist ein umgekehrtes Problem, aber wenn Sie eine kleine App haben, die Remoting in einer großen App durchführen möchte, ist dies nützlich. Ein weiteres Problem ist, dass Sie wissen müssen, dass Sie assemblyB beim Starten Ihrer App verwenden und dass es nur einmal pro Ausführung funktioniert. Es gibt also unterschiedliche Probleme, und Sie können Ihren Ansatz je nach Ihrer Situation wählen. Bitte beachten Sie, dass es sich um eine nicht unterstützte/nicht dokumentierte Funktion handelt, die jedoch in EF-Kerntools verwendet wird, sodass sie vorerst "brauchbar" ist ...

0
Yepeekai