Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Question
Monday, October 31, 2016 9:48 AM
How to verify c# Assembly Implements a Certain Interface?
private static void LoadPlugins(IList<Assembly> assemblies)
{
DirectoryInfo dInfo = new DirectoryInfo(GetExtensionsDirectory());
FileInfo[] files = dInfo.GetFiles("*.dll");
if (null != files)
{
foreach (FileInfo file in files)
{
string[] fileArray = file.Name.Split('.');
if (fileArray != null)
{ // I want to verify if assembly implements a IPlugin interface before adding assemebly.
assemblies.Add(Assembly.Load(fileArray[0]));
}
}
}
}
pianoboyCoder
All replies (38)
Monday, October 31, 2016 9:57 AM | 1 vote
See if this is what you are looking for
http://stackoverflow.com/questions/26733/getting-all-types-that-implement-an-interface
Please remember to mark the replies as answers if they help and unmark them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.
VB Forums - moderator
Monday, October 31, 2016 10:09 AM | 1 vote
Hi,
a small extension to Karens good reply:
An assembly does not implement an interface. It can contain types, that implements the interface. I think this is an important point to understand.
With kind regards,
Konrad
Monday, October 31, 2016 3:45 PM | 1 vote
I don't think there's a safe way to do it. And Wow, this was way harder than I thought.
Basically, for each dll you discover, you need to do these steps:
- Create an app domain
- Load the assembly and any dependencies into the app domain.
- Get the types in the assembly with assembly.GetTypes
- Filter the assemblies down to those that are IPlugins by selecting types where typeof(IPlugin).IsAssignableFrom(typeof(someType)) or you can use someType.GetInterfaces().Contains(typeof(IPlugin))
- Record the names of any suitable plugin types in this assembly. (Add them to a list?)
- Unload the AppDomain.
Then you will have a list of suitable assembly paths and type names. You can then decide whether you want to load them or not, and into which appdomain.
But I don't know how to do it without so aggressively loading each assembly and all its dependencies.
If you already have a plugins folder and the strategy is to load all the assemblies anyway, then it's easier to just load everything into the current appdomain and just use GetTypes to locate the plugin types. Because the hard parts are: 1) resolving the dependencies (particularly hard if you use ReflectionOnlyLoadFrom because you have to load the dependencies manually). and 2) Unloading the Appdomain into which you triaged any unwanted plugins.
Monday, October 31, 2016 6:32 PM | 1 vote
This problem is what MEF and MAF were designed for. Please take a look at using one or both of these.
Thursday, November 3, 2016 8:22 AM
This is what I have so far. Is there a robust way to do this below
private static void LoadPlugins(IList<Assembly> assemblies)
{
DirectoryInfo dInfo = new DirectoryInfo(GetExtensionsDirectory());
FileInfo[] files = dInfo.GetFiles("*.dll");
if (files != null)
{
foreach (FileInfo file in files)
{
string[] fileArray = file.Name.Split('.');
if (fileArray != null)
assemblies.Add(Assembly.Load(fileArray[0]));
}
IList<Assembly> copyAssemblies = assemblies;
foreach (var assembly in copyAssemblies.ToList())
{
foreach (var assemblyType in assembly.GetExportedTypes())
{
int index = assembly.GetExportedTypes().ToList().IndexOf(assemblyType);
var implementsInterface = typeof(IPluginContract).IsAssignableFrom(assemblyType) && assemblyType.IsClass;
//If class assembly does not have IPluginContract, remove assembly from list
if (!implementsInterface && !assemblyType.IsInterface)
{
assemblies.RemoveAt(index);
}
}
}
}
}
pianoboyCoder
Thursday, November 3, 2016 11:25 AM
Since this is an "extensions" directory, is it fair to assume that it's OK to load every DLL in that directory at the same time? That's the only concern that I have. I can't tell if your job is to simply enumerate all the extensions and then ultimately select one to load later, or if the job is to load all extensions.
This works if there are expected to be very few extensions, but if you have lots of extensions, then you probably need to do it in a loop with some kind of feedback to the user about what the heck is going on. It takes a non-trivial amount of time to load assemblies.
But more importantly, you said that you want to verify that an assembly supports an interface before adding it. Your code will do that only for your list of assemblies. You have successfully created a list of the loaded assemblies, that match some criteria. It will not actually prevent loading the unsupported assemblies into memory. And it will not unload the non-matching assemblies from memory. What do you want to have happen with the non-matching junk DLL's?
So I ask you with great concern: under what circumstances are you expecting to encounter a DLL in your extensions directory that does not support the specified interface?
It's like inviting everyone you can see into your house, but then only playing games with the people you know. There are still strangers in your house!
[a person in your house is like a loaded assembly, in this analogy - in case that's not obvious.]
Thursday, November 3, 2016 1:36 PM
Basically I am trying to prevent loading dlls that is not expected to be loaded in memory because I will get exception on the on My MEF compose parts if someone make an mistake an copy an Dll into the Extesnion Directory. My compose part function blow up. So I am trying put in exception handling for bad Dlls and keep loading the assemblies that's need to be loaded. What is someone make an mistake copy a c++ or c sharp dll that does not even apart of my project. What do I do?
So what will be the appropriate logic to doing this below to what I am trying to accomplish?
private static void LoadPlugins(IList<Assembly> assemblies)
{
DirectoryInfo dInfo = new DirectoryInfo(GetExtensionsDirectory());
FileInfo[] files = dInfo.GetFiles("*.dll");
if (files != null)
{
foreach (FileInfo file in files)
{
string[] fileArray = file.Name.Split('.');
if (fileArray != null)
assemblies.Add(Assembly.Load(fileArray[0]));
}
IList<Assembly> copyAssemblies = assemblies;
foreach (var assembly in copyAssemblies.ToList())
{
foreach (var assemblyType in assembly.GetExportedTypes())
{
int index = assembly.GetExportedTypes().ToList().IndexOf(assemblyType);
var implementsInterface = typeof(IPluginContract).IsAssignableFrom(assemblyType) && assemblyType.IsClass;
//If class assembly does not have IPluginContract, remove assembly from list
if (!implementsInterface && !assemblyType.IsInterface)
{
assemblies.RemoveAt(index);
}
}
}
}
}
pianoboyCoder
Thursday, November 3, 2016 1:50 PM
You're code looks reasonable but I really think you're trying to solve a boundary case issue. Assuming you have an app installed into the standard installation directory for programs then a user cannot "accidentally" copy a file into your plugins directory. Your program will be installed under Program Files which normal users cannot write to. If the accidentally copy a file into the folder they'd also have to confirm they want to do that. If that do they then it wasn't an accident.
Step back and reconsider your approach. Loading assemblies in a separate appdomain is useful if you want to isolate your plugins from the rest of the app but introduces issues with sharing data (hence MAF). If you just want to use a separate appdomain so you can reflect against the exported types looking for certain types with interfaces then your code is going have a slow load time. Doable, but slow. Take a look at some of the more common approaches to loading plugins.
The first approach has you loading only assemblies that have a special extension (ex. myplugin). Under the hood these files are really just .dll files but with a different file extension. The advantage of this approach is that you can have both plugins and third-party support assemblies in the same directory. You enumerate and then load all the plugin files without having to worry about the other dlls. There is no reason to unload files in this approach. You'll still have to enumerate the exported types looking for the plugin class itself but this only occurs on the binaries that you know to be plugins.
The second approach gives you more control over plugins and speeds up loading at the cost of maintainability. In this approach you don't load dlls to find plugins but instead you load metadata. The metadata could be in the registry (if you use an installer) or simply files on disk. The metadata file can be as simple as a text file that specifies the plugin name, assembly it is contained in and the fully qualified type name. You could even expand it over time to allow for additional information (like dependencies). Your app loads the metadata files in whatever directory(ies) you want. Then you load the corresponding dlls and create an instance of the specified, fully qualified type. No need for reflection and load times are pretty fast. Even better, if you are working on a new plugin and, for whatever reason, you aren't quite ready to use it you can remove the metadata file (or rename it, or expose an option to "disable", etc) and the plugin wouldn't load.
Michael Taylor
http://www.michaeltaylorp3.net
Thursday, November 3, 2016 2:36 PM
I agree with you. This will resolve the issue, if someone still accidentally make a mistake an copy dll in the extension directory with the exported types. I just tested and try to break my logic below. I just simply throw and exception and continue processing the foreach.
foreach (FileInfo file in files)
{
string[] fileArray = file.Name.Split('.');
if (fileArray != null)
{
try
{
assemblies.Add(Assembly.Load(fileArray[0]));
}
catch (Exception e)
{
MessageBox.Show(e.Message);
continue;
}
}
}
pianoboyCoder
Thursday, November 3, 2016 3:47 PM
If you use Mono.Cecil you don't have to load the assembly for checking. An example:
static bool IsPlugin(string AssemblyName)
{
bool ok = false;
try
{
ModuleDefinition m = ModuleDefinition.ReadModule(AssemblyName);
foreach (TypeDefinition t in m.Types)
{
var n = t.Interfaces.FirstOrDefault((x) => x.FullName.EndsWith(".IPlugin"));
if (n != null)
{
ok = true;
break;
}
}
}
catch (Exception)
{
ok = false;
}
return ok;
}
Thursday, November 3, 2016 6:06 PM | 1 vote
I am not forsure why you searching for the ".IPlugin". The assembly name may not contain have IPlugin , but also be a plugin.
pianoboyCoder
Thursday, November 3, 2016 10:01 PM | 1 vote
I am not forsure why you searching for the ".IPlugin". The assembly name may not contain have IPlugin , but also be a plugin.
pianoboyCoder
You said: "// I want to verify if assembly implements a IPlugin interface before adding assemebly."
The code searches for a Type the implements "Whatever.IPlugin"
Friday, November 4, 2016 5:46 AM | 1 vote
To Explain using the example below. I want to load all assemblies that's implementing the IPluginContract Interface. So if you look at the assemblies below only Assembly 1, 2, 3 should load successfully. Assembly should error out or not load because it does not Implement IPluginInterface. So how can I change the my LoadPlugin method to do that. At the moment I am just continue if the assembly does not load and throws and error
foreach (FileInfo file in files)
{
string[] fileArray = file.Name.Split('.');
if (fileArray != null)
{
try
{
assemblies.Add(Assembly.Load(fileArray[0]));
}
catch (Exception e)
{
MessageBox.Show(e.Message);
continue;
}
}
}
Assembly 1:
public class ClassA : IPluginContract
{
}
Assembly 2:
public class ClassB : IPluginContract
{
}
Assembly 3:
public class ClassC : IPluginContract
{
}
Assembly 4:
public class ClassD
{
}
pianoboyCoder
Friday, November 4, 2016 7:49 AM | 1 vote
To use my example you need Mono.Cecil.dll.
Change ".IPlugin" into ".IPluginContract" or use its fully qualified name.
And then:
foreach (FileInfo file in files)
{
if (IsPlugin(file.FullName))
{
// load assembly file.FullName
}
}
Friday, November 4, 2016 12:41 PM | 1 vote
Can you send me a link to The Mono.Cecil.Dll. I want to read up on this. Do the Mono.Cecil.dll have an IsPlugin method?
pianoboyCoder
Friday, November 4, 2016 12:49 PM | 1 vote
Can you send me a link to The Mono.Cecil.Dll. I want to read up on this.
pianoboyCoder
http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/
Do the Mono.Cecil.dll have an IsPlugin method?
pianoboyCoder
IsPlugin is the function i wrote earlier in this thread.
To summarize:
using Mono.Cecil;
using Mono.Collections.Generic;
static bool IsPlugin(string AssemblyName)
{
bool ok = false;
try
{
ModuleDefinition m = ModuleDefinition.ReadModule(AssemblyName);
foreach (TypeDefinition t in m.Types)
{
var n = t.Interfaces.FirstOrDefault((x) =>
x.FullName.EndsWith(".IPluginContract"));
if (n != null)
{
ok = true;
break;
}
}
}
catch (Exception)
{
ok = false;
}
return ok;
}
foreach (FileInfo file in files)
{
if (IsPlugin(file.FullName))
{
// load assembly file.FullName
}
}
Friday, November 4, 2016 1:44 PM | 1 vote
Step back and reconsider your approach. [...]
I think I've finally rallied right right support, Michael (@CoolDadTx) gets what I was talking about.
@CoolDadTx, I appreciate you elaborating on it the way you did. I'm thinking the same as you, but you've called out the relevant issues in more detail than I did. Thanks for that.
Saturday, November 5, 2016 5:07 AM | 1 vote
So I have a question. Will ITest, ITest2 be a m.Type in the foreach below. If so, then the solution is not what I want. For the simple reason, The foreach will be iterating through interfaces that's implemented and not the class type that implements the IpluginInterface.
I need the forloop to only check the class types to see if the class type implements a specific interface.
Assembly 2:
public class ClassB : IPluginContract, ITest, ITest2
{
}
ModuleDefinition m = ModuleDefinition.ReadModule(AssemblyName);
foreach (TypeDefinition t in m.Types)
{
var n = t.Interfaces.FirstOrDefault((x) =>
x.FullName.EndsWith(".IPluginContract"));
if (n != null)
{
ok = true;
break;
}
}
pianoboyCoder
Saturday, November 5, 2016 8:36 AM | 1 vote
So I have a question. Will ITest, ITest2 be a m.Type in the foreach below. If so, then the solution is not what I want. For the simple reason, The foreach will be iterating through interfaces that's implemented and not the class type that implements the IpluginInterface.
I need the forloop to only check the class types to see if the class type implements a specific interface.
Assembly 2:
public class ClassB : IPluginContract, ITest, ITest2
{
}
pianoboyCoder
For each type T in: ClassB, IPluginControl, ITest, ITest2,
If T's interface collection contains an interface that ends with .IPluginContract
type T will be the type that implements ???.IPluginContract
Only when T = ClassB a plugin is found.
Saturday, November 5, 2016 4:04 PM | 1 vote
So my question is, in the forloop will ITest, ITest2 show up as type in the forloop?
pianoboyCoder
Sunday, November 6, 2016 3:26 AM
You see the difference. This is something I am trying to do. I want to verify if the type is apart of the assembly, if the assembly implements the IPluginContract interface, then I load the assembly. This works perfect below. I tried to break method and it will not break. Review below. Everything is working as expected. What is your take on the logic below?
Type type = typeof(IPluginContract);
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type assemblyType in asm.GetTypes())
{
try
{
if (!type.IsAssignableFrom(assemblyType))
continue;
else
assemblies.Add(Assembly.Load(asm.GetName().Name));
}
catch(Exception e)
{
//Log error and continue
Console.WriteLine(e.Message);
continue;
}
}
}
pianoboyCoder
Sunday, November 6, 2016 7:22 AM
So my question is, in the forloop will ITest, ITest2 show up as type in the forloop?
pianoboyCoder
They will show up, but the fact the they show up is not enough to infer that the assembly contains a plugin. In fact, the if statement inside the loop will filter them out as they don't have an interface called IPluginContract.
In your example, if ClassB doesn't implement IPluginContract the assembly is not reconized as a plugin container.
Monday, November 7, 2016 7:56 AM
You are Correct. So my example below is valid and it works.
Type type = typeof(IPluginContract);
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type assemblyType in asm.GetTypes())
{
try
{
if (!type.IsAssignableFrom(assemblyType))
continue;
else
assemblies.Add(Assembly.Load(asm.GetName().Name));
}
catch(Exception e)
{
//Log error and continue
Console.WriteLine(e.Message);
continue;
}
}
}
If Class B does not implement the IPluginContract, then its not a plugin container. But in this case below, its a plugin container. Because it implements the IPluginContract.
Assembly 2:
public class ClassB : IPluginContract, ITest, ITest2
{
}
pianoboyCoder
Monday, November 7, 2016 10:03 AM
You are Correct. So my example below is valid and it works.
pianoboyCoder
It doesn't solve your problem.
Your problem is "// I want to verify if assembly implements a IPlugin interface before adding assemebly."
It turns out that to use your solution and check the plugins you have to load all the assemblies. But once you've loaded the assemblies in your current domain you can't unload them.
https://msdn.microsoft.com/en-us/library/mt632258.aspx
The result is that, in the end, you know which assembly contains a plugin, but you loaded all the assemblies anyway.
If memory serves me, Wyck suggested to load all the assemblies in another domain, test them and then unload the domain and all the assemblies with it. Then you can load the "good" assemblies in your current domain. It could be a slow operation though, especially if you have to scan a lot of assemblies. Hence i suggested that you avoid loading the assemblies and test them with Mono.Cecil**.
**
Monday, November 7, 2016 10:39 AM
I dont need to unload the plugins. Why would I need to unload the plugins that is meant to be use.
pianoboyCoder
Monday, November 7, 2016 10:49 AM
I dont need to unload the plugins. Why would I need to unload the plugins that is meant to be use.
pianoboyCoder
Some post above, you wrote:
assemblies.Add(Assembly.Load(fileArray[0]));
in a loop. So you loaded all the assemblies.
Then you inspected the assembly collection, "assemblies", checking for those containing a plugin. But you never unloaded the unwanted assemblies.
Monday, November 7, 2016 10:54 AM
Thats because I never loaded the unwanted plugins
pianoboyCoder
Monday, November 7, 2016 10:55 AM
I am only loaded the expected plugins and thats the plugins thats implemented the IPluginContract and along with the Current Domain assembly.
pianoboyCoder
Monday, November 7, 2016 11:04 AM
Thats because I never loaded the unwanted plugins
pianoboyCoder
How do you skip unwanted assemblies if you don't load and test them?
Monday, November 7, 2016 11:10 AM
By this logic below
if (!type.IsAssignableFrom(assemblyType))
continue;
pianoboyCoder
Monday, November 7, 2016 11:16 AM
By this logic below
if (!type.IsAssignableFrom(assemblyType))
continue;pianoboyCoder
If you refer to one of your last versions of the source code you posted, before getting there you wrote:
AppDomain.CurrentDomain.GetAssemblies()
so the list of assemblies you check is already loaded in your CurrentDomain.
Monday, November 7, 2016 11:20 AM
This is the logic
Type type = typeof(IPluginContract);
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type assemblyType in asm.GetTypes())
{
try
{
if (!type.IsAssignableFrom(assemblyType))
continue;
else
assemblies.Add(Assembly.Load(asm.GetName().Name));
}
catch(Exception e)
{
//Log error and continue
Console.WriteLine(e.Message);
continue;
}
}
}
pianoboyCoder
Monday, November 7, 2016 11:27 AM
This is the logic
Type type = typeof(IPluginContract);
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type assemblyType in asm.GetTypes())
{
try
{
if (!type.IsAssignableFrom(assemblyType))
continue;
else
assemblies.Add(Assembly.Load(asm.GetName().Name));
}
catch(Exception e)
{
//Log error and continue
Console.WriteLine(e.Message);
continue;
}
}
}pianoboyCoder
Who loads the assemblies in CurrentDomain? The ones you explore in "foreach ... asm"
Monday, November 7, 2016 11:58 AM
This is what I am doing. I load the executing assembly
IList<Assembly> assemblies = new List<Assembly>(10);
var executingAssembly = Assembly.GetExecutingAssembly();
assemblies.Add(Assembly.GetExecutingAssembly());
Type type = typeof(IPluginContract);
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type assemblyType in asm.GetTypes())
{
try
{
if (!type.IsAssignableFrom(assemblyType))
continue;
else
assemblies.Add(Assembly.Load(asm.GetName().Name));
}
catch(Exception e)
{
//Log error and continue
Console.WriteLine(e.Message);
continue;
}
}
}
pianoboyCoder
Monday, November 7, 2016 1:05 PM
This is what I am doing. I load the executing assembly
pianoboyCoder
In your first post you showed a method called GetExtensionsDirectory. Perhaps that's the place where your "plugin" assemblies are located.
How come, when you only load the executing assembly, do you think all those assemblies in GetExtensionsDirectory get loaded?
CurrentDomain.GetAssemblies only gives you the assemblies you referenced before you compiled your program.
Monday, November 7, 2016 6:13 PM
Correct. At the moment, I commented out the the GetExtensionDirectory logic to try something out. However, I am loading the assemblies that I am referencing in my current app domain and only compose the parts that implements the IPluginContract.
Now that I think about it, I am going to go back to my old way of doing it which gives me a lot more extensiblity below. Tell me what do you think below.
DirectoryInfo dInfo = new DirectoryInfo(GetExtensionsDirectory());
FileInfo[] files = dInfo.GetFiles("*.dll");
if (files != null)
{
foreach (FileInfo file in files)
{
string[] fileArray = file.Name.Split('.');
if (fileArray != null)
{
try
{
assemblies.Add(Assembly.Load(fileArray[0]));
}
catch (Exception e)
{
MessageBox.Show(e.Message);
continue;
}
}
}
pianoboyCoder
Monday, November 7, 2016 6:37 PM
Correct. At the moment, I commented out the the GetExtensionDirectory logic to try something out. However, I am loading the assemblies that I am referencing in my current app domain and only compose the parts that implements the IPluginContract.
Now that I think about it, I am going to go back to my old way of doing it which gives me a lot more extensiblity below. Tell me what do you think below.
DirectoryInfo dInfo = new DirectoryInfo(GetExtensionsDirectory());
FileInfo[] files = dInfo.GetFiles("*.dll");if (files != null)
{
foreach (FileInfo file in files)
{
string[] fileArray = file.Name.Split('.');if (fileArray != null)
{
try
{
assemblies.Add(Assembly.Load(fileArray[0]));
}
catch (Exception e)
{
MessageBox.Show(e.Message);
continue;
}
}
}pianoboyCoder
In this way you load all the assemblies and you cannot unload the unwanted ones.
Monday, November 7, 2016 7:24 PM
I'm kind of laughing right now because I mentioned all this before. Cook the pasta enough and it will stick to the wall I guess.
How about filtering DLLs by either:
1) Only loading the ones that are in the extensions directory.
2) Only loading ones that match a certain name.
3) Only loading ones that have some extra versioninfo tag (metadata)
Then load ALL the assemblies that match that filter.
At that point, when you attempt to create an object in the assembly that implements a certain interface, you can either:
1) Throw an exception (and ultimately display an error or log a message) if the type doesn't exist in the loaded assembly, or it isn't of the correct type, or doesn't implement the specified interface.
2) Just go ahead and try to create the object (you'll get an exception in Activator.CreateInstance if things go badly) and then cast it to the desired interface (you'll get an exception if it doesn't work.)
Your program can validate what's in the extensions folder and gather enough information to tell the user that they have bogus or unsupported extension dlls in their extensions folder and they should FIX THE PROBLEM (because they just eat up memory if they're loaded but unusable).
LET GO of your approach of deciding if an assembly is appropriate to load by using the technique of loading it and asking questions about the types inside it via reflection. Because once you've loaded it to decide if it's appropriate or not, it's too late -- it's loaded already and cannot be unloaded (Unless you go through the steps that I originally outlined.)