web-dev-qa-db-fra.com

Comment charger des assemblys situés dans un dossier de l'application .net Core Console

Je crée une application console sur la plate-forme .Net Core et je me demandais comment charger des assemblys (fichiers .dll) et instancier des classes à l'aide de fonctions dynamiques C #. Cela semble tellement différent de .Net 4.X et ce n’est pas vraiment documenté ...

Par exemple, disons que j'ai une bibliothèque de classes (.Net Core) qui n'a qu'une classe:

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

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

Donc, le nom du fichier dll serait MyClassLib.dll et son situé dans /dlls/MyClassLib.dll.

Maintenant, je souhaite charger ceci dans une application console simple (.Net Core), instancier la classe Sample et appeler les méthodes à l'aide des fonctionnalités dynamiques de C # dans l'application console suivante:

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

Note: Par .Net Core, je veux dire la version RC2.

26
Vahid Amiri

Je ne sais pas si c'est la meilleure façon de le faire, mais voici ce que j'ai trouvé:

( Testé uniquement sur .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 est l'espace de noms et Sample est le type aka nom de classe.

Une fois exécuté, il essaiera de charger la bibliothèque de classes compilée SampleClassLib.dll dans la mémoire et donnera à mon application console l'accès à MyClassLib.SampleClasses.Sample (jetez un coup d'œil à la question) puis mon application appelle la méthode SayHello() et lui donne le nom "John Doe", Par conséquent, le programme imprime:

"Hello John Doe"

Remarque rapide: Le remplacement de la méthode Load n'a pas d'importance, vous pouvez donc tout simplement remplacer son contenu par throw new NotImplementedException() et il ne devrait affecter en rien les choses qui nous intéressent.

17
Vahid Amiri

Actuellement en cours d'exécution avec netcoreapp1.0, vous n'avez pas réellement besoin de mettre en œuvre votre propre AssemblyLoader. Il existe une Default qui fonctionne très bien. (D'où @ VSG24 en mentionnant que la Load ne fait rien).

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

avec project.json ressemblant à:

{
  "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

Merci pour votre partage. Il travaille également avec Net Core 1.0. Si votre assembly a besoin d'autres assemblys dans le même chemin, vous pouvez utiliser l'exemple de code ci-dessous.

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

N'oubliez pas d'ajouter les dépendances suivantes à votre fichier project.json:

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

En utilisant .net core 1.1/standard 1.6, j’ai constaté que AssemblyLoader n’était pas disponible, et 

AssemblyLoadContext.Default.LoadFromAssemblyPath (assemblyPath)
m'a donné une erreur "Impossible de charger le fichier ou l'assembly xxx". 

Enfin, cette solution ci-dessous a fonctionné pour moi - en ajoutant simplement une étape pour obtenir l’objet AssemblyName. J'espère que cela aidera tous ceux qui sont bloqués:

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

@Rob, la seule façon pour vous de construire votre exemple est de changer votre type "myInstance" en dynamic

Laisser le type var autorise le code à se construire, mais dès que j'essaie d'utiliser une méthode de l'assembly chargé à l'exécution, des erreurs de compilation telles que myInstance ne contiennent pas la méthode X. Je suis nouveau dans ce domaine, mais le fait de marquer le type comme étant dynamique semble avoir du sens. Si le type est chargé au moment de l'exécution, comment le compilateur peut-il vérifier que myInstance contiendra la méthode X ou prop Y? En saisissant myInstance comme dynamique, je pense que vous supprimez la vérification du compilateur et que je pourrais donc obtenir l’exemple pour construire et exécuter parfaitement. Pas sûr que ce soit à 100% la bonne manière (je ne sais pas assez et vous pouvez dire qu'il y a un problème d'utilisation de dynamic?) Mais c'est le seul moyen de le faire fonctionner sans avoir à me donner la peine de créer le mien AssemblyLoader (comme vous le signalez correctement).

Alors...

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

J'espère que cela aide quelqu'un qui est nouveau, il m'a fallu un bon bout de temps pour comprendre pourquoi monInstance en tant que var n'avait pas la méthode X, etc. Doh!

1
Mortoman