Proper way of loading assemblies that implement an interface? - c#

I want to do something like this:
Create a number of assemblies that implement IMyInterface.
In the web.config or app.config of my application, define which one of them to load (they may have different names)
When the application starts, load the assembly as marked in web.config and use its implementation of IMyInterface
what is the best way of doing this?
I am using Framework 3.5 at this time.
(p.s. I know I can just define a variable that contains the assembly name (e.g. key="My assembly", value="myassembly.dll" then dynamically load the assembly, just wanting to know if there is a more correct way of doing this)

Assemblies do not implement interfaces; types do.
For ASP.NET apps, I suggest putting every assembly in the /bin directory and use a web.config configuration option and Activator.CreateInstance to create the actual type:
var typeName = "MyNamespace.MyClass, MyAssembly"; // load from config file
IMyInterface instance =
(IMyInterface)Activator.CreateInstance(Type.GetType(typeName));

I do this as follows (i use base class, with abstract methods instead interfaces):
1 - List the classes implemented with my base class.
Assembly assembly = null;
AssemblyName assemblyName = new AssemblyName();
assemblyName.CodeBase = "FullPathToAssembly";
assembly = Assembly.Load(assemblyName);
Type[] arrayTipo;
arrayTipo = assembly.GetTypes();
var tipos =
from t in arrayTipo
where t.BaseType == typeof(DD.Util.BaseProcesso)
select t;
2 - Call the method of execution in an instance of the class:
Type tipo = engine.GetType("FullClassName");
BaseProcesso baseProcesso = (BaseProcesso)Activaor.CreateInstance(tipo, new object[] { "ConstructorParameter1", "ConstructorParameter1") });
// Event
baseProcesso.IndicarProgresso += new BaseProcesso.IndicarProgressoHandler(baseProcesso_IndicarProgresso);
new Thread(new ThreadStart(baseProcesso.Executar)).Start();
Works for me!

Related

AppDomain Assembly not found when loaded from byte array

Please bear with me, I spent 30+ hours trying to get this work - but without success.
At the start of my program I load an Assembly (dll) in bytearray and delete it afterwards.
_myBytes = File.ReadAllBytes(#"D:\Projects\AppDomainTest\plugin.dll");
Later on in the program I create a new Appdomain, load the byte array and enumerate the types.
var domain = AppDomain.CreateDomain("plugintest", null, null, null, false);
domain.Load(_myBytes);
foreach (var ass in domain.GetAssemblies())
{
Console.WriteLine($"ass.FullName: {ass.FullName}");
Console.WriteLine(string.Join(Environment.NewLine, ass.GetTypes().ToList()));
}
The types get correctly listed:
ass.FullName: plugin, Version=1.0.0.0, Culture=neutral,PublicKeyToken=null
...
Plugins.Test
...
Now I want to create an instance of that type in the new AppDomain
domain.CreateInstance("plugin", "Plugins.Test");
This call results in System.IO.FileNotFoundException and I don't know why.
When I look in ProcessExplorer under .NET Assemblies -> Appdomain: plugintest I see that the assembly is loaded correctly in the new appdomain.
I suspect the exception to occur because the assembly is searched again on disk. But why does the program want to load it again?
How can I create an instance in a new appdomain with an assembly loaded from byte array?
The main problem here is thinking that you can instantiate a plugin while executing code in your primary appdomain.
What you need to do instead, is create a proxy type which is defined in an already loaded assembly, but instantiated in the new appdomain. You can not pass types across app domain boundaries without the type's assembly being loaded in both appdomains. For instance, if you want to enumerate the types and print to console as you do above, you should do so from code which is executing in the new app domain, not from code that is executing in the current app domain.
So, lets create our plugin proxy, this will exist in your primary assembly and will be responsible for executing all plugin related code:
// Mark as MarshalByRefObject allows method calls to be proxied across app-domain boundaries
public class PluginRunner : MarshalByRefObject
{
// make sure that we're loading the assembly into the correct app domain.
public void LoadAssembly(byte[] byteArr)
{
Assembly.Load(byteArr);
}
// be careful here, only types from currently loaded assemblies can be passed as parameters / return value.
// also, all parameters / return values from this object must be marked [Serializable]
public string CreateAndExecutePluginResult(string assemblyQualifiedTypeName)
{
var domain = AppDomain.CurrentDomain;
// we use this overload of GetType which allows us to pass in a custom AssemblyResolve function
// this allows us to get a Type reference without searching the disk for an assembly.
var pluginType = Type.GetType(
assemblyQualifiedTypeName,
(name) => domain.GetAssemblies().Where(a => a.FullName == name.FullName).FirstOrDefault(),
null,
true);
dynamic plugin = Activator.CreateInstance(pluginType);
// do whatever you want here with the instantiated plugin
string result = plugin.RunTest();
// remember, you can only return types which are already loaded in the primary app domain and can be serialized.
return result;
}
}
A few key points in the comments above I will reiterate here:
You must inherit from MarshalByRefObject, this means that the calls to this object can be proxied across app-domain boundaries using remoting.
When passing data to or from the proxy class, the data must be marked [Serializable] and also must be in a type which is in the currently loaded assembly. If you need your plugin to return some specific object to you, say PluginResultModel then you should define this class in a shared assembly which is loaded by both assemblies/appdomains.
Must pass an assembly qualified type name to CreateAndExecutePluginResult in its current state, but it would be possible to remove this requirement by iterating the assemblies and types yourself and removing the call to Type.GetType.
Next, you need to create the domain and run the proxy:
static void Main(string[] args)
{
var bytes = File.ReadAllBytes(#"...filepath...");
var domain = AppDomain.CreateDomain("plugintest", null, null, null, false);
var proxy = (PluginRunner)domain.CreateInstanceAndUnwrap(typeof(PluginRunner).Assembly.FullName, typeof(PluginRunner).FullName);
proxy.LoadAssembly(bytes);
proxy.CreateAndExecutePluginResult("TestPlugin.Class1, TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
}
Going to say this again because it's super important and I didn't understand this for a long time: when you're executing a method on this proxy class, such as proxy.LoadAssembly this is actually being serialized into a string and being passed to the new app domain to be executed. This is not a normal function call and you need to be very careful what you pass to/from these methods.
This call results in System.IO.FileNotFoundException and I don't know why. I suspect the exception to occur because the assembly is searched again on disk. But why does the program want to load it again?
The key here is understanding loader contexts, there's an excellent article on MSDN:
Think of loader contexts as logical buckets within an application domain that hold assemblies. Depending on how the assemblies were being loaded, they fall into one of three loader contexts.
Load context
LoadFrom context
Neither context
Loading from byte[] places the assembly in the Neither context.
As for the Neither context, assemblies in this context cannot be bound to, unless the application subscribes to the AssemblyResolve event. This context should generally be avoided.
In the code below we use the AssemblyResolve event to load the assembly in the Load context, enabling us to bind to it.
How can I create an instance in a new appdomain with an assembly loaded from byte array?
Note this is merely a proof of concept, exploring the nuts and bolts of loader contexts. The advised approach is to use a proxy as described by #caesay and further commented upon by Suzanne Cook in this article.
Here's an implementation that doesn't keep a reference to the instance (analogous to fire-and-forget).
First, our plugin:
Test.cs
namespace Plugins
{
public class Test
{
public Test()
{
Console.WriteLine($"Hello from {AppDomain.CurrentDomain.FriendlyName}.");
}
}
}
Next, in a new ConsoleApp, our plugin loader:
PluginLoader.cs
[Serializable]
class PluginLoader
{
private readonly byte[] _myBytes;
private readonly AppDomain _newDomain;
public PluginLoader(byte[] rawAssembly)
{
_myBytes = rawAssembly;
_newDomain = AppDomain.CreateDomain("New Domain");
_newDomain.AssemblyResolve += new ResolveEventHandler(MyResolver);
}
public void Test()
{
_newDomain.CreateInstance("plugin", "Plugins.Test");
}
private Assembly MyResolver(object sender, ResolveEventArgs args)
{
AppDomain domain = (AppDomain)sender;
Assembly asm = domain.Load(_myBytes);
return asm;
}
}
Program.cs
class Program
{
static void Main(string[] args)
{
byte[] rawAssembly = File.ReadAllBytes(#"D:\Projects\AppDomainTest\plugin.dll");
PluginLoader plugin = new PluginLoader(rawAssembly);
// Output:
// Hello from New Domain
plugin.Test();
// Output:
// Assembly: mscorlib
// Assembly: ConsoleApp
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
Console.WriteLine($"Assembly: {asm.GetName().Name}");
}
Console.ReadKey();
}
}
The output shows CreateInstance("plugin", "Plugins.Test") is successfully called from the default app domain, although it has no knowledge of the plugin assembly.
Have you tried providing the Assemblies full name, In your case
domain.CreateInstance("plugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Plugins.Test");

Loading the same Assembly twice but with different version

I have one assembly that is called asm.dll.
This assembly has the version 1.0.0.0 (set within AssemblyInfo.cs)
Then I need do do some code modifications in that assembly (still asm.dll), advance the version to 2.0.0.0 and build it again.
Now, I have two files named asm.dll that differ with respect to some code modifications and a their version number.
How do I load these two files during runtime?
ADDENDUM:
Right now I am trying the following:
var asm1 = Assembly.LoadFrom("dir1\asm.dll");
var asm2 = Assembly.LoadFrom("dir2\asm.dll");
var types1 = asm1.GetTypes();
var types2 = asm2.GetTypes();
Type type1 = types1.First<Type>(t => t.Name.Equals("myClassIWantToInstantiate"));
Type type2 = types2.First<Type>(t => t.Name.Equals("myClassIWantToInstantiate"));
MyObject myObject1 = (MyObject1)Activator.CreateInstance(type, new object[] { });
MyObject myObject2 = (MyObject2)Activator.CreateInstance(type, new object[] { });
But I get the following behavior:
the first call to Activator.CreateInstance(...) correctly returns the requested instance for myObject1
the second call to Activator.CreateInstance(...) returns again myObject1 instead of myObject2
The code compiles and the program runs without exception or observable problems, except that I do not get myObject2
I am aware of this answer and I think the code I used, is the same, only a bit newer (correct me, if I am wrong).
In your answer, you are using Activator.CreateInstance for both objects - this is using whatever is registered globally. I believe the types loaded from the specific assemblies will not be enough to do this.
In the answer you linked, the assemblies are loaded using Assembly.LoadFile rather than LoadFrom, and CreateInstance is called on the assembly instance, rather than using the static Activator.CreateInstance. Have you tried this?
var asm1 = Assembly.LoadFile("dir1\asm.dll");
var asm2 = Assembly.LoadFile("dir2\asm.dll");
MyObject myObject1 = (MyObject)asm1.CreateInstance("myClassIWantToInstantiate");
MyObject myObject2 = (MyObject)asm2.CreateInstance("myClassIWantToInstantiate");

Activator.CreateInstance throws Exception "The system cannot find the file specified."

I've copied code for an assembly that is used in a solution to create a similar assembly. The GUTS was different, but the shell stayed the same.
These assemblies are used in a project at a client that are add-on's and not part of our core code. Now that I'm finished the assembly does not want to load like it is supposed to.
The code that loads the assembly is
var assemblyName = ((XmlElement)xmlDoc.GetElementsByTagName("AssemblyName")[0]).InnerText;
var qualifiedClass = ((XmlElement)xmlDoc.GetElementsByTagName("QualifiedClass")[0]).InnerText;
IExternalAddOn addOn = (IExternalAddOn)Activator.CreateInstance(assemblyName, qualifiedClass).Unwrap();
var properties = new Dictionary<Type, object>();
properties[typeof(DevExpress.XtraBars.Ribbon.RibbonControl)] = mainForm.ribbon;
var form = addOn.ShowForm(properties);
if (form != null)
{
form.MdiParent = mainForm;
form.Text = pListRow.NAME;
form.Show();
I get the exception on the CreateInstance part.
The interesting thing is that when I use
Assembly ass = Assembly.LoadFrom(assemblyName); // this is test code
Type at = ass.GetType(qualifiedClass);
IExternalAddOn addOn = (IExternalAddOn)Activator.CreateInstance(at);
to load the assembly and get the type, and CreateInstance it works.
Why do you need the Unwrap? Is there a difference in the two different ways of loading? And WHY does the first one not work?
Thanks
J
As soon as Assembly.LoadFrom works, I guess you are passing a file name as an assemblyName parameter, which is wrong in case of Activator.CreateInstance(assemblyName, qualifiedClass).
According to Activator.CreateInstance documentation:
assemblyName can be either of the following:
The simple name of an assembly, without its path or file extension. For example, you would specify TypeExtensions for an assembly whose path and name are .\bin\TypeExtensions.dll.
The full name of a signed assembly, which consists of its simple name, version, culture, and public key token; for example, "TypeExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=181869f2f7435b51".

Ninject + Auto Discovery

Here's the deal: I want to create a C# console app. When run, this app will look in a particular folder for dll's with classes that implement a particular interface, and then run a method on those dll's.
I haven't done this before but from my reading that should be "easy enough" from an IoC/Ninject perspective. I think you can do something with kernel.Bind() to load assemblies of a certain interface in a certain directory. I think/hope I can figure that part out (if you know otherwise, please do tell!).
But here is my quandary.
Here is a visual to help first:
MainProgramFolder
-- MainProgram.exe
-- MainProgram.exe.config
-- LibraryFolder
----- Library1Folder
--------Library1.dll
--------Library1.dll.config
----- Library2Folder
--------Library2.dll
--------Library2.dll.config
The dll's that implement this interface are technically stand alone apps -- they are just libraries instead of exe's (or, rather, I'd like them to be for IoC purposes). I'd like for them to be run in their own context, with their own app.configs. So for example, MainProgram.exe would bind the ILibrary interface to classes inside Library1.dll and Library2.dll because they implement ILibrary. But inside Library1, it calls ConfigurationManager to get its settings. When I call Class.Method() for each of the bindings from MainProgram, how can I ensure they are referencing their own .config's and not MainProgram.exe.config? (Also, fwiw, these additional libraries will likely not be a part of the assembly or even namespace of the main programs -- we're basically providing a drop folder for an application to kind of "subscribe" to the main program's execution.)
IOW, I know you can attach an app.config to a class library but I wouldn't know how, after the bindings have been resolved from the IOC, to make those dll's "see" its own config rather than the main program's config.
All thoughts appreciated!
Thanks
Tom
First, to load and bind all of your classes you'll need ninject.extensions.conventions, and something like this:
var kernel = new StandardKernel();
/*add relevant loop/function here to make it recurse folders if need be*/
kernel.Bind(s => s.FromAssembliesMatching("Library*.dll")
.Select(type => type.IsClass && type.GetInterfaces().Contains(typeof(ILibrary)))
.BindSingleInterface()
.Configure(x=>x.InSingletonScope()));
To make each instance load its configuration as if it was the entry point you will need to run it in a new app domain. Your ILibrary implementation needs to inherit MarshalByRefObject and be Serializable so that it will run correctly in the alternate appdomain
[Serializable]
public class LibraryA :MarshalByRefObject, ILibrary
You can then add this activation strategy to your kernel that will cause it to swap out instances of ILibrary with an instance loaded in an alternate appdomain with your config file convention before they are returned.
public class AlternateAppDomainStrategy<T> : ActivationStrategy
{
public override void Activate(IContext context, InstanceReference reference)
{
if (reference.Instance.GetType().GetInterfaces().Contains(typeof(T)))
{
var type = reference.Instance.GetType();
var configFilePath = type.Assembly.GetName().Name + ".dll.config";
var file = new FileInfo(configFilePath);
if (file.Exists)
{
var setup = new AppDomainSetup() { ConfigurationFile = file.FullName, ApplicationBase = AppDomain.CurrentDomain.BaseDirectory };
var domain = AppDomain.CreateDomain(type.FullName, null, setup);
var instance = domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
reference.Instance = instance;
}
else
{
throw new FileNotFoundException("Missing config file", file.FullName);
}
}
}
}
And Add it to your kernel
kernel.Components.Add<IActivationStrategy, AlternateAppDomainStrategy<ILibrary>>();
From there you can simply instantiate your ILibrary instances and call methods on them. They will load in their own app domains with their own configs. It gets a lot more complicated if you need to pass things in/out of the instance either via methods or constructor, but from the sound if it you don't so this should be OK.
var libs = kernel.GetAll<ILibrary>();
foreach (var lib in libs)
{
lib.Method();
}

Using reflection to dynamically query an assembly

I am having difficulty with using reflections dynamically eg. query a .exe file without requiring a reference to be added for every assembly which I wish to query against.
So for instance, the code below is the regular way to get a hold of a class to then be checked.
AssemblyName assembly_name = new AssemblyName( "Name" );
The issue is not adding the argument in to the code but the code requirng direct reference to the new assembly to check against.
Any suggestions are welcome.
It sounds like you're really just trying to load an assembly at execution time. Look at Assembly.Load and Assembly.ReflectionOnlyLoad.
Maybe you're looking for something like Cecil. It's a library (available on Windows and other platforms) that allows to query metadata without the need to resolve all references.
I'm not really sure what you mean by "query". If you want to know how to create an instance from an assembly using reflection, here is an example:
// From within the current assembly
public CartesianType CreateInstance(string fullyQualifiedClassName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Type target = assembly.GetType(fullyQualifiedClassName, true, true);
return (CartesianType)Activator.CreateInstance(target);
}
// From an external assembly already referenced in your project
public SomeClass CreateInstance(string fullyQualifiedClassName)
{
Assembly assembly = Assembly.GetAssembly(typeof(SomeClass));
Type target = assembly.GetType(fullyQualifiedClassName, true, true);
return (SomeClass)Activator.CreateInstance(target);
}
All other methods must use Load or LoadFile, LoadFrom etc.

Categories