How to Load Assemblies Located in a Folder in .Net Core Console App

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

Not sure if it's the best way to do it but here's what I came up with:

(Only tested on .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 is the namespace and Sample is the type aka class name.

When executed, it will try to load the SampleClassLib.dll compiled class library in the memory and gives my console app access to MyClassLib.SampleClasses.Sample (take a look at the question) then my app calls the method SayHello() and passes "John Doe" as name to it, Therefore the program prints:

"Hello John Doe"

Quick note:
The override for the method Load doesn't matter so you might as well just replace its content with throw new NotImplementedException() and it shouldn't affect anything we care about.

How to dynamically load assemblies in dotnet core

I found a solution to my problem.

It turned out, that I had the property PreserveCompilationContext set to true, and that's why the debugger wouldn't register my manually copied assembly.
When I removed the property from the web project csproj file, everything worked.

Load assemblies with references from subfolders at runtime

TL;DR;

You are looking for AssemblyResolve event of the AppDomain. If you are loading all the plugin assemblies in the current app domain, then you need to handle the event for AppDomain.CurrentDomain and load the requested assembly in the event handler.

No matter what folder structure you have for references, what you should do is:

  • Get all assembly files form plugins folder
  • Get all assembly files from references folder (entire hierarchy)
  • Handle AssemblyResolve of AppDomain.CurrentDomain and check if the requested assembly name is available files of reference folder, then load and return assembly.
  • For each assembly file in plugins folder, get all types and if the type implements your plugin interface, instantiate it and call its entry point for example.


Example

In this PoC I load all implementations of IPlugin dynamically at run-time from assemblies in Plugins folder and after loading them and resolving all dependencies at run-time, I call SayHello method of plugins.

The application which loads plugins, doesn't have any dependency to plugins and just loads them at run-time from the following folder structure:

Sample Image

This is what I did for loading, resolving and calling the plugins:

var plugins = new List<IPlugin>();
var pluginsPath = Path.Combine(Application.StartupPath, "Plugins");
var referencesPath = Path.Combine(Application.StartupPath, "References");

var pluginFiles = Directory.GetFiles(pluginsPath, "*.dll",
SearchOption.AllDirectories);
var referenceFiles = Directory.GetFiles(referencesPath, "*.dll",
SearchOption.AllDirectories);

AppDomain.CurrentDomain.AssemblyResolve += (obj, arg) =>
{
var name = $"{new AssemblyName(arg.Name).Name}.dll";
var assemblyFile = referenceFiles.Where(x => x.EndsWith(name))
.FirstOrDefault();
if (assemblyFile != null)
return Assembly.LoadFrom(assemblyFile);
throw new Exception($"'{name}' Not found");
};

foreach (var pluginFile in pluginFiles)
{
var pluginAssembly = Assembly.LoadFrom(pluginFile);
var pluginTypes = pluginAssembly.GetTypes()
.Where(x => typeof(IPlugin).IsAssignableFrom(x));
foreach (var pluginType in pluginTypes)
{
var plugin = (IPlugin)Activator.CreateInstance(pluginType);
var button = new Button() { Text = plugin.GetType().Name };
button.Click += (obj, arg) => MessageBox.Show(plugin.SayHello());
flowLayoutPanel1.Controls.Add(button);
}
}

And this is the result:

Sample Image

You can download or clone the code:

  • Clone r-aghaei/loadpluginassembly
  • Download zip file

.NET Core Assembly.LoadFile at PostBuild event

I'm found solution at github issue. Message by amits1995 and angelcalvasp.

I'm added <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> to my csproj and using this code to load assembly:

public static class AssemblyLoader
{
public static Assembly LoadFromAssemblyPath(string assemblyFullPath)
{
var fileNameWithOutExtension = Path.GetFileNameWithoutExtension(assemblyFullPath);
var fileName = Path.GetFileName(assemblyFullPath);
var directory = Path.GetDirectoryName(assemblyFullPath);

var inCompileLibraries = DependencyContext.Default.CompileLibraries.Any(l => l.Name.Equals(fileNameWithOutExtension, StringComparison.OrdinalIgnoreCase));
var inRuntimeLibraries = DependencyContext.Default.RuntimeLibraries.Any(l => l.Name.Equals(fileNameWithOutExtension, StringComparison.OrdinalIgnoreCase));

var assembly = (inCompileLibraries || inRuntimeLibraries)
? Assembly.Load(new AssemblyName(fileNameWithOutExtension))
: AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyFullPath);

if (assembly != null)
LoadReferencedAssemblies(assembly, fileName, directory);

return assembly;
}

private static void LoadReferencedAssemblies(Assembly assembly, string fileName, string directory)
{
var filesInDirectory = Directory.GetFiles(directory).Where(x => x != fileName).Select(x => Path.GetFileNameWithoutExtension(x)).ToList();
var references = assembly.GetReferencedAssemblies();

foreach (var reference in references)
{
if (filesInDirectory.Contains(reference.Name))
{
var loadFileName = reference.Name + ".dll";
var path = Path.Combine(directory, loadFileName);
var loadedAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
if (loadedAssembly != null)
LoadReferencedAssemblies(loadedAssembly, loadFileName, directory);
}
}
}
}

Usage:

public static void Main(string[] args)
{
var dllPath = args[0]; // "D:\Test\bin\Release\netcoreapp2.0\my.dll"
var assembly = AssemblyLoader.LoadFromAssemblyPath(dllPath);
var types = assembly.GetExportedTypes(); // No exceptions
}


Related Topics



Leave a reply



Submit