How to Load an Assembly to Appdomain with All References Recursively

How to Load an Assembly to AppDomain with all references recursively?

You need to invoke CreateInstanceAndUnwrap before your proxy object will execute in the foreign application domain.

 class Program
{
static void Main(string[] args)
{
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);

Type type = typeof(Proxy);
var value = (Proxy)domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName);

var assembly = value.GetAssembly(args[0]);
// AppDomain.Unload(domain);
}
}

public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFile(assemblyPath);
}
catch (Exception)
{
return null;
// throw new InvalidOperationException(ex);
}
}
}

Also, note that if you use LoadFrom you'll likely get a FileNotFound exception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder. Use LoadFile to load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.

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

How to load this assembly into my AppDomain?

You should load the assemblies in the AssemblyResolve event handler of your current domain, like explained here

Is there a way to force all referenced assemblies to be loaded into the app domain?

This seemed to do the trick:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
var loadedPaths = loadedAssemblies.Select(a => a.Location).ToArray();

var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList();

toLoad.ForEach(path => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));

As Jon noted, the ideal solution would need to recurse into the dependencies for each of the loaded assemblies, but in my specific scenario I don't have to worry about it.


Update: The Managed Extensibility Framework (System.ComponentModel) included in .NET 4 has much better facilities for accomplishing things like this.

Load Assembly with dependent references

A code snippet to resolve assemblies at runtime:

private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {

String DllName = new AssemblyName(args.Name).Name + ".dll";

return Assembly.LoadFile(DllName);
}

Set it at the beginning of your your plugin initializer:

AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

No error checking is included.

The actual assembly can be loaded from any path, the example above loads it from the current working directory. You can load it from embedded resource as well.

Load Assembly into AppDomain

The most basic multidomain scenario is

static void Main()
{
AppDomain newDomain = AppDomain.CreateDomain("New Domain");
newDomain.ExecuteAssembly("file.exe");
AppDomain.Unload(newDomain);
}

Calling ExecuteAssembly on a seperate domain is convienient but does not offer the ability to interact with the domain itself. It also requires the target assembly to be an executable and forces the caller to a single entry point. To incorporate some flexibility you could also pass a string or args to the .exe.

I hope this helps.

Extension: Try something like the following then

AppDomainSetup setup = new AppDomainSetup();
setup.AppDomainInitializer = new AppDomainInitializer(ConfigureAppDomain);
setup.AppDomainInitializerArguments = new string[] { unknownAppPath };
AppDomain testDomain = AppDomain.CreateDomain("test", AppDomain.CurrentDomain.Evidence, setup);
AppDomain.Unload(testDomain);
File.Delete(unknownAppPath);

where the AppDomain can be initilised as follows

public static void ConfigureAppDomain(string[] args)
{
string unknownAppPath = args[0];
AppDomain.CurrentDomain.DoCallBack(delegate()
{
//check that the new assembly is signed with the same public key
Assembly unknownAsm = AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(unknownAppPath));

//get the new assembly public key
byte[] unknownKeyBytes = unknownAsm.GetName().GetPublicKey();
string unknownKeyStr = BitConverter.ToString(unknownKeyBytes);

//get the current public key
Assembly asm = Assembly.GetExecutingAssembly();
AssemblyName aname = asm.GetName();
byte[] pubKey = aname.GetPublicKey();
string hexKeyStr = BitConverter.ToString(pubKey);
if (hexKeyStr == unknownKeyStr)
{
//keys match so execute a method
Type classType = unknownAsm.GetType("namespace.classname");
classType.InvokeMember("MethodNameToInvoke", BindingFlags.InvokeMethod, null, null, null);
}
});
}

Loading assembly in another appdomain from another folder with dependencies

Ok in the end i found the culprit.
Since i call GetAssemblies on the Domain, it at the same time loads them in the AppDomain (calling the resolver) AND in my current root domain (which needs to load the assemblies too as it gets the assembly objects as a return of GetAssemblies)
To fix it i had to set the resolver both locally and on the remote domain, however this is a bad idea as it removes the isolation advantage of loading in another AppDomain, so i settled for not calling GetAssemblies and doing the reflection work on the Child domain and returning the results back to the original domain.



Related Topics



Leave a reply



Submit