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
ofAppDomain.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:
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:
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
C# VS Java Enum (For Those New to C#)
Conditional Compilation and Framework Targets
Entering Keys Manually with Entity Framework
Getting the Size of a Field in Bytes with C#
"The Remote Certificate Is Invalid According to the Validation Procedure." Using Gmail Smtp Server
Associating Enums with Strings in C#
Is Double Multiplication Broken in .Net
Convert a List to a String in C#
Callback When Dependency Property Recieves Xaml Change
Most Elegant Way to Generate Prime Numbers
How to Deserialize Xml to Object
Differences in String Compare Methods in C#
Why Is It Bad to Use an Iteration Variable in a Lambda Expression
How to Know If a Process Is Running
Easiest Way to Create a Cascade Dropdown in ASP.NET MVC 3 with C#