28

I'm looking for a way to run code by executing the following steps:

  1. Receiving a list of NuGet packages (a list of tuples ("package name", "package version", "path to main class").
  2. Retrieving them in a local directory (cf code sample #1)
  3. Loading them in my program at run-time
  4. Running the main classes by introspection (cf code sample #2)

By now I am struggling with the third step. I can't find out how to load my package at run-time.

My main question are:

  • How can I find out in which folders were stored the retrieved packages?
  • How can I load the content of those directories into my program?

Code Sample #1:

private static void getPackageByNameAndVersion(string packageID, string version)
{
    IPackageRepository repo =
            PackageRepositoryFactory.Default
                  .CreateRepository("https://packages.nuget.org/api/v2");

   string path = "C:/tmp_repo";
   PackageManager packageManager = new PackageManager(repo, path);
   Console.WriteLine("before dl pkg");
   packageManager.InstallPackage(packageID, SemanticVersion.Parse(version));

}

Code sample #2:

private static void loadByAssemblyNameAndTypeName(string assemblyName, string typeName)
{
   AppDomain isolationAppDomain = AppDomain.CreateDomain("tmp");
   object a = isolationAppDomain.CreateInstanceAndUnwrap(assemblyName, typeName);
   Type x = a.GetType();
   MethodInfo m = x.GetMethod("Main");
   m.Invoke(a, new object[] { });
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Manuel Leduc
  • 1,849
  • 3
  • 23
  • 39

2 Answers2

35

Grab a cup of coffee :)

Downloading the nuget package?

Nuget.Core (nuget package) is a good choice, and here is a snippet of code that I have that should be able to download a nuget package by id and version

var repo = PackageRepositoryFactory.Default
                .CreateRepository("https://packages.nuget.org/api/v2");

string path = "c:\\temp";
var packageManager = new PackageManager(repo, path);
packageManager.PackageInstalled += PackageManager_PackageInstalled;

var package = repo.FindPackage("packageName", SemanticVersion.Parse("1.0.0"));
if (package != null)
{
    packageManager.InstallPackage(package, false, true);
}

Notice that I plugged an event handler to the PackageInstalled event of the PackageManager class.

How do we load an assembly in an isolated app domain?

Since reflection API does not provide a way to load an assembly in a specific domain, We will create a proxy class that act as a loader in our isolated domain:

public class TypeProxy : MarshalByRefObject
{
    public Type LoadFromAssembly(string assemblyPath, string typeName)
    {
        try
        {
            var asm = Assembly.LoadFile(assemblyPath);
            return asm.GetType(typeName);
        }
        catch (Exception) { return null; }
    }
}

And now, is how to put it all together?

Here comes the complex part:

private static void PackageManager_PackageInstalled(object sender, 
                                                    PackageOperationEventArgs e)
{
    var files = e.FileSystem.GetFiles(e.InstallPath, "*.dll", true);
    foreach (var file in files)
    {
        try
        {
            AppDomain domain = AppDomain.CreateDomain("tmp");
            Type typeProxyType = typeof(TypeProxy);
            var typeProxyInstance = (TypeProxy)domain.CreateInstanceAndUnwrap(
                    typeProxyType.Assembly.FullName,
                    typeProxyType.FullName);

            var type = typeProxyInstance.LoadFromAssembly(file, "<KnownTypeName>");
            object instance = 
                domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
        }
        catch (Exception ex)
        {
            Console.WriteLine("failed to load {0}", file);
            Console.WriteLine(ex.ToString());
        }

    }
}

Notice that this method is the event handler that gets executed after downloading the nuget package

Also

Note that you will need to replace <KnownTypeName> with the expected type name coming from the assembly (or maybe run a discovery of all public types in the assembly)


Worth noting that I haven't executed this code myself and cannot guarantee that it will work out of the box, and still might need some tweaking. but Hopefully it is the concept that allows you to solve the problem.

Kjartan
  • 18,591
  • 15
  • 71
  • 96
Bishoy
  • 3,915
  • 29
  • 37
  • 3
    One problem with your example is that PackageManager_PackageInstalled is only called when the asked package is installed for the first time in c:\\temp. If it is already there from a previous code execution, PackageManager_PackageInstalled will not be call. – Manuel Leduc Aug 20 '15 at 09:22
-8

Don't do that! You are probably trying to load NuGet content at a customers computer to save some space on distribution of your software. Isn't it that?

The common recommended approach is to download the NuGet content as the second step of an automated build (after downloading the source code), build the software and run the automated tests with the NuGet content you have downloaded. And then distribute the build with the NuGet content you have tested as the complex whole unit.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tomas Kubes
  • 23,880
  • 18
  • 111
  • 148
  • 6
    I understand your concerns but the main goal of the software I'm writing is to load software components at runtime. So loaded codes will be third partie's I cannot know at build time. But thanks for this wise advice anyway :) – Manuel Leduc Aug 17 '15 at 07:57