Aloha
Given a plug-in architecture (C# / .NET 3.5) with the plug-ins stored in the GAC, how can I list/load all dll's that contain types that implement my specific interface? In other words, I'd like to investigate which plug-ins for my application are installed on a machine, by looking in the GAC.
-Edoode
To add to BFree's answer, I suggest that you could load the assemblies for reflection only. This gives you enhanced security (the assemblies aren't able to execute at all) without appdomains, and flexibility (you can load assemblies that are for a different architecture).
First a little clarification: a DLL cannot implement an interface. The DLL contains types that could implement a specific interface. Here's a .NET wrapper around fusion.dll that allows you to enumerate all the assemblies in the GAC. Once you have loaded the assembly with Assembly.Load you can try to find all the types that implement the specific interface:
foreach (var type in assembly.GetTypes())
{
var myInterfaceType = typeof(IMyInterface);
if (type != myInterfaceType && myInterfaceType.IsAssignableFrom(type))
{
Console.WriteLine("{0} implements IMyInterface", type);
}
}
The Gac is really just a directory on your machine like any other. Here's the typical breakdown:
c:\windows\assembly\GAC
\GAC_32
\GAC_MSIL
And maybe some others..
Within each of those folders, you'll find subfolders where the actual dll's are stored. You'll first need to write a recursive method to get you all the dll's found under \assembly\, (you can find these online easily if you're not comfortable doing it yourself). Once you have that list of dll's you can proceed to use Assembly.LoadFile to load up the assembly and check all the types to see if they implement a given interface.
My only suggestion would be to load up these dll's in a seperate appdomain so that you're not allowing any potential harmful dll's to get loaded into your app.
Some links:
Searching Directories.
Loading Assemblies and checking for a given interface.
Creating new AppDomain.
You should look at the Type Selector Tool in Enterprise Library. It's probably not what you want directly, but it does what you are describing and you might be able to borrow some implementation from it.
First off, I'd recommend not doing this. To do this, you have to load all the assemblies from the GAC. I'd recommend you have your user (or an admin, or whatever) tell you what assemblies to try to load from (though for that, you might want a list of all the options, which might be why you're asking this...)
That said, this might work, but it's throwing errors for several assemblies it should work for, and I'm not sure why.... Also, I'm not sure how to detect where the GAC is -- c:\windows\assembly is the default, but I don't know where the real value is stored (registry?)
var targetType = typeof(IComparable);
var errors = new List<Exception>();
var c = Directory.GetFiles(#"c:\windows\assembly", "*.dll", SearchOption.AllDirectories).ToList()
.ConvertAll(f => Path.GetFileNameWithoutExtension(f))
.Where(f => !f.EndsWith(".ni"))
.Distinct().ToList()
.ConvertAll(f => { try { return Assembly.ReflectionOnlyLoad(f); } catch (Exception ex) { errors.Add(ex); return null; } })
.Where(a => a != null)
.SelectMany(a => { try { return a.GetTypes(); } catch (Exception ex) { errors.Add(ex); return new Type[] { }; } })
.Where(t => targetType.IsAssignableFrom(t));
Good luck with that.
Related
I'm implementing a framework for C# on ASP.NET using Dotnet 6. I want part of the framework to be extensible by outside parties; they just need to implement a few classes and we can integrate their work via Nuget or direct assembly reference.
Part of what they need to complete is a Registration Class so they can define how their engine should be registered with the dependency injection container. This is an example of what I'd expect 3rd parties to supply:
public class EchoServiceRegistration : IRegisterDI
{
public IServiceCollection Register(IServiceCollection serviceCollection)
{
serviceCollection.TryAddSingleton<EchoEngine>();
return serviceCollection;
}
}
In the consuming application, I'm looking for all of the classes that implement the IRegisterDI interface using the AppDomain class, which is a riff off this SO answer https://stackoverflow.com/a/26750/2573109. (Note: I switched to using name-based matching just for troubleshooting and will change it back to a better implementation once properly solved):
List<Type> allRegistrationClasses = AppDomain
.CurrentDomain
.GetAssemblies()
.SelectMany(it => it.GetTypes())
.Where(tp => tp.GetInterfaces()
.Any(inter => inter.Name == nameof(IRegisterDI)))
.ToList();
As written, this returns 0 types.
On the next troubleshooting iteration, I proved that the registration class is available to the caller. So, I manually created an instance of the EchoServiceRegistration class, as seen below. This time, the AppDomain contains a single entry for EchoServiceRegistration (as expected).
var allRegistrationClasses = AppDomain.CurrentDomain.GetAssemblies().SelectMany(it => it.GetTypes()).Where(tp => tp.GetInterfaces().Any(inter => inter.Name == nameof(IRegisterDI))).ToList();
var echoServiceRegistration = new EchoServiceRegistration();
echoServiceRegistration.Register(builder.Services);
if (allRegistrationClasses.Count is not 1) throw new InvalidOperationException(); // Does not throw an exception
To prove I didn't accidentally fix something, I commented out the two lines related to Echo Service Registration, and allRegistrationClasses again contains 0 records.
var allRegistrationClasses = AppDomain.CurrentDomain.GetAssemblies().SelectMany(it => it.GetTypes()).Where(tp => tp.GetInterfaces().Any(inter => inter.Name == nameof(IRegisterDI))).ToList();
// var echoServiceRegistration = new EchoServiceRegistration();
// echoServiceRegistration.Register(builder.Services);
if (allRegistrationClasses.Count is not 1) throw new InvalidOperationException(); // Throws an exception
My gut reaction is I don't understand how the AppDomain determines what assemblies to load. I started reading the documentation Microsoft provides about it, but it seems like a deep topic and I won't have a more clear understanding without quite a bit of dedicated reading.
How do I guarantee the full set of classes are available when calling this during the DI Container build? Please let me know if I can provide any additional detail or clarity.
It looks to me like on your first test, the assembly is not loaded into the app domain because it's not used.
On your second test, you are manually creating an instance so you are forcing the loading of the assembly. Note that even if the assembly is added as a reference, it won't be loaded unless it's actually needed.
You need to explicitly load the assemblies for them to be scanned. One way of doing this would be to have all extension libraries live in a particular folder from which you can then load them before registration:
var files = Directory.GetFiles(#"C:\my-framework\extensions");
foreach (var file in files)
{
var extension = Path.GetExtension(file);
if(extension == ".dll")
{
var asm = Assembly.LoadFile(file);
Console.WriteLine($"Loaded extension [{asm.FullName}]");
}
}
Once the assemblies are loaded, you should get the desired results.
This is an overly-simplified example but it should be enough to get you going.
I develop a system with plugins, which loads assemblies at runtime. I have a common interface library, which i share between server and its plugins. But, when i perform LoadFrom for plugin folder and try to find all types, which implement common interface IServerModule i get runtime exception:
The type 'ServerCore.IServerModule' exists in both 'ServerCore.dll'
and 'ServerCore.dll'
I load plugins like this:
foreach (var dll in dlls)
{
var assembly = Assembly.LoadFrom(dll);
var modules = assembly.GetExportedTypes().Where(
type => (typeof (IServerModule)).IsAssignableFrom(type)
&& !type.IsAbstract &&
!type.IsGenericTypeDefinition)
.Select(type => (IServerModule)Activator.CreateInstance(type));
result.AddRange(modules);
}
How can i deal with this trouble?
I'll be gratefull for any help
Inspect the problem DLL and its dependencies. Chances are good that it is pulling in ServerCore.dll from a different version of .NET than your main application.
I recommend you use MEF if you want to do plugins.
Well, my solution is ugly, but works and i'll go forward for MEF in future (maybe). For now, i added such thing:
if(Path.GetFileNameWithoutExtension(dll)==Assembly.GetCallingAssembly().GetName().Name)
continue;
Thanks everybody for awesome replies
EDIT: I came up to more elegant solution, here it is:
var frameworkAssemblies =
from file in new DirectoryInfo(frameworkDirectory).GetFiles()
where (file.Extension.ToLower() == ".dll" || file.Extension.ToLower() == ".exe")
&& !AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).Contains(file.GetFileNameWithoutExtension())
select Assembly.LoadFrom(file.FullName);
I have created Visual Studio 2012 Package (using VS2012 SDK). This Extension (if installed on the client's IDE environment) should has, among other things, a functionality of collecting all specific types from currently opened solution which developer is working on. A similar feature is embedded in Visual Studio Designer for ASP.NET MVC Application Project, where developer implements a Model/Controller class, build a project, and then is able to access this type in Scaffolding UI (Designer's dropdown list). The corresponding features are also available in WPF, WinForms Visual Designers, etc.
Let's say that my extension has to collect all types from current solution, which implement ISerializable interface. The steps are following: Developer creates specific class, rebuild containing project/solution, then do some action provided by extension UI, thus involves performing ISerializabletypes collecting.
I have tried to implement collecting operation using reflection:
List<Type> types = AppDomain.CurrentDomain.GetAssemblies().ToList()
.SelectMany(s => s.GetTypes())
.Where(p => typeof(ISerializable).IsAssignableFrom(p) && !p.IsAbstract).ToList();
But above code causes System.Reflection.ReflectionTypeLoadException exception to be thrown:
System.Reflection.ReflectionTypeLoadException was unhandled by user code
HResult=-2146232830
Message=Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
Source=mscorlib
StackTrace:
at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
at System.Reflection.RuntimeModule.GetTypes()
at System.Reflection.Assembly.GetTypes()
(...)
LoaderException: [System.Exception{System.TypeLoadException}]
{"Could not find Windows Runtime type 'Windows.System.ProcessorArchitecture'.":"Windows.System.ProcessorArchitecture"}
(...)
How can I properly implement operation of collecting specific types from currently built solution?
I was trying to do a similar thing and unfortunately the only way around this I've found so far is by doing the following (which I feel is a bit messy, but maybe with some tweaking for a specific situation might be ok)
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
IEnumerable<Type> types = assemblies.SelectMany(x => GetLoadableTypes(x));
...
public static IEnumerable<Type> GetLoadableTypes(Assembly assembly)
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null);
}
}
This would give you all types, but you can filter out whatever you wish.
Referenced this post: How to prevent ReflectionTypeLoadException when calling Assembly.GetTypes()
I'm not sure if I understand you correctly, but if I am this will make it:
var assembly = Assembly.GetExecutingAssembly();
IEnumerable<Type> types =
assembly.DefinedTypes.Where(t => IsImplementingIDisposable(t))
.Select(t => t.UnderlyingSystemType);
........
private static bool IsImplementingIDisposable(TypeInfo t)
{
return typeof(IDisposable).IsAssignableFrom(t.UnderlyingSystemType);
}
I have to create an application to read the name of all DLLs (assemblies) in my application path along with its version. And also to read the same of all the dll in the sub folders.
How to do this in C#. Can any one help me with sample code?
EDIT : how to read details of Legacy dlls( External dlls- not created in .NET)
Thanks in advance.
You should search through your given root directory by calling Directory.GetFiles(). You can run through the result and load every assembly by calling Assembly.ReflectionOnlyLoadFrom() (cause if you load it that way it won't be added to the AppDomain, thous no unload is needed).
With these Assembly classes you can access the GetName() function and take a look into the Version property to get the version information.
Other properties, that are not easily to get, can be reached through the GetCustomAttribute() function like this:
((AssemblyCopyrightAttribute)assembly.GetCustomAttribute(typeof(AssemblyCopyrightAttribute), true).Copyright
With these informations you should be able to built up the list you like.
Update:
And here's the obligatory linq code sample:
var rootPath = #"C:\MyRoot\Folder";
var query = Directory.GetFiles(rootPath, "*.dll", SearchOption.AllDirectories)
.Select(fileName =>
{
try
{
return Assembly.ReflectionOnlyLoadFrom(fileName);
}
catch
{
return null;
}
})
.Where(assembly => assembly != null)
.Select(assembly => new
{
Version = assembly.GetName().Version.ToString(),
Name = assembly.GetName().Name
});
foreach (var infos in query)
{
Console.WriteLine(infos.Name + " " + infos.Version);
}
Update 2:
So to get it from normal DLLs you should take a look into this question.
I'm a bit confused on what you actually want, but check the Assembly class: http://msdn.microsoft.com/en-us/library/system.reflection.assembly.aspx
Use Assembly.Load. There is one problem, you can't unload it, so if you do it too much times your memory will be filled by garbage.
If you call AssemblyName.GetAssemblyName, the assembly doesn't get loaded into your appdomain.
To include subfolders, you'll likely need to write a recursive function. Directory.GetDirectories or DirectoryInfo.GetDirectories can be used to retrieve all subfolders.
Use Assembly.GetName() to get an object, where you can extract the assembly version, as one part of an assemblies name is its version.
As already mentioned above, an assembly loaded with Assembly.Load cannot be unloaded and therefore remains in memory. You can however create a separate AppDomain, which you can unload again. Data transfer between AppDomains is possible by passing serializable objects, which is no problem for you because you just want to pass a string.
If you actually want to load the assemblies in the context of an extendable application, have a look at MEF.
Best Regards,
Oliver Hanappi
Use Assembly.ReflectionOnlyLoadFrom if you don't need to execute any code from that assembly and you want only gather some info about members contained inside.
We implement a plugin framework for our application and load plugin assemblies using Assembly.Loadfrom. We then use GetTypes() and further examine the types with each plugin file for supported Interfaces.
A path for the plugins is provided by the user and we cycle through each of the files in the folder to see if it (the plugin) supports our plugin interface. If it does, we create an instance, if not we move onto the next file.
We build two versions of software from the one code base (appA_1 and appA_2).
Loading the plugins works well when the plugins are loaded by the application that was built at the same time as the plugin file. However if we build appA_2 and point to the plugin folder of appA_1, we get an exception when GetTypes() is called.
A basic version of our code is;
var pluginAssembly = Assembly.LoadFrom(FileName);
foreach (var pluginType in pluginAssembly.GetTypes())
{
We get a "ReflectionTypeLoadException" exception.
This is concerning because we want our application to be able to load the types of any plugin, built by anyone. Is there something we are missing?
EDIT:
After iterating through the LoaderExceptions we have discovered that there is a single file libPublic.dll that generates a System.IO.FileNotFoundException exception. The strange thing is that this file resides in the application directory and the plugin is referenced to the project file.
EDIT 2:
In the exception log we find the following
"Comparing the assembly name resulted in the mismatch: Revision Number"
A few things:
Make sure you don't have duplicate assemblies in the plugin directory (i.e. assemblies that you're already loading in your main app from your app directory.) Otherwise, when you load your plugin, it may load an additional copy of the same assembly. This can lead to fun exceptions like:
Object (of type 'MyObject') is not of type 'MyObject'.
If you're getting the exception when instantiating a type, you may need to handle AppDomain.AssemblyResolve:
private void App_Startup(object sender, StartupEventArgs e)
{
// Since we'll be dynamically loading assemblies at runtime,
// we need to add an appropriate resolution path
// Otherwise weird things like failing to instantiate TypeConverters will happen
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var domain = (AppDomain) sender;
foreach (var assembly in domain.GetAssemblies())
{
if (assembly.FullName == args.Name)
{
return assembly;
}
}
return null;
}
I realize it's a bit strange to have to tell the CLR that, in order to resolve an assembly, find the assembly with the name we're using to resolve, but I've seen odd things happen without it. For example, I could instantiate types from a plugin assembly, but if I tried to use TypeDescriptor.GetConverter, it wouldn't find the TypeConverter for the class, even though it could see the Converter attribute on the class.
Looking at your edits, this is probably not what's causing your current exception, though you may run into these issues later as you work with your plugins.
Thanks to this post I could solve the ReflectionTypeLoadException that I was getting in a UITypeEditor. It's a designer assembly (a winforms smart-tag used at design-time) of a custom class library, that scan for some types.
/// <summary>
/// Get the types defined in the RootComponent.
/// </summary>
private List<Type> getAssemblyTypes(IServiceProvider provider)
{
var types = new List<Type>();
try
{
IDesignerHost host = (IDesignerHost)provider.GetService(typeof(IDesignerHost));
ITypeResolutionService resolution = (ITypeResolutionService)provider.GetService(typeof(ITypeResolutionService));
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
foreach (var assembly in ((AppDomain)sender).GetAssemblies())
{
if (assembly.FullName == args.Name)
{
return assembly;
}
}
return null;
};
Type rootComponentType = resolution.GetType(host.RootComponentClassName, false);
types = rootComponentType.Assembly.GetTypes().ToList();
}
catch
{
}
return types;
}
You are getting an assembly version mismatch. Since your plugins refer to this libPublic.dll, you must version it carefully and in particular not bump its revision/build/etc. numbers at every compile.