Assembly.GetTypes() throwing an exception - c#

What does assembly GetTypes() do behind the scenes? Assuming an assembly has been loaded to the AppDomain does it still need to read from the physical DLL? And what the assembly manifest do?
Iterating through the assembly like this:
AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
I'm occasionally getting the following error:
Could not load file or assembly
Which tells me that because an assembly is loaded into the AppDomain it's not necessarily fully loaded to memory. Sometimes it still needs to go back to the file.
My questions:
Why is it doing that?
What can I do to detect these semi-loaded assemblies?

Getting a type from a assembly may require additional assemblies to be loaded so that is most probably the reason for the error; a failure to load a dependent assembly. However a .NET assembly may be constructed from several modules in different files so I believe you may also face this problem if you have a multifile assembly and one or more of the files are missing or corrupt.
Associated with the error you should get more information about the specific assembly that could not be loaded.
If you just want to load a list of the loadable types in the assembly you can use a extension method like this:
public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly)
{
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null);
}
}
(Source: Get All Types in an Assembly)

Behind the scenes, GetType method returns the address stored in the specified object's type object pointer member (When the object is stored in the heap, this information is stored along with couple of others like Sync Block Index). This is how the GetType method returns the true type of any object. An assembly might be dependent on some other assembly that must be loaded. And unless it is required by the application, it won't be loaded by JIT. So, yes, it requires the assemblies to be physically available at all times.

This question is a bit old, but leaving a note here that GetTypes might throw an exception if the application has not loaded the assembly dependencies especially in the newer .NET Core/.NET 5/6 .dlls.
Microsoft has introduced{assemblyName}.deps.json file which contains dependencies for an assembly, so you will need to load those too before you can call GetTypes without getting an exception.
I found these blog posts to be helpful on how to do that:
https://jeremybytes.blogspot.com/2020/01/using-typegettype-with-net-core.html
https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support#load-plugins

Related

Proper dependency loading for plug-ins using Assembly.Load, Assembly.LoadFile

I am working on a plug-in based framework that allows plug-ins to exchange data between one another based on some contract (interface).
Currently, a Windows Service can load the plug-ins in two ways:
In the same AppDomain as the Windows Service hosting the plug-ins
In another process that the Windows service communicates with via named pipes (WCF).
This works nicely most of the time. However, there are certain scenarios where one plug-in might reference an assembly, and another plug-in references a newer version of the assembly. In this scenario, I always want the newer version of the dependency to be loaded regardless of which plug-in is loaded first.
Here is the folder structure:
Windows Service Directory (AppDomainBase)
Plugins
Plugin1
Plugin1.dll
SharedDependency.dll (1.0.0.0)
Plugin2
Plugin2.dll
SharedDependency.dll (1.0.1.0)
I have done a lot of research already, and tried many different things. That said:
I cannot redirect assembly bindings via an app.config file. Although this works, it is not practical because I do not know all of the dependencies ahead of time and cannot add every single one to the app.config.
I can't use the GAC
I don't want multiple versions of the same assembly loaded, just the newest one.
I have read about Assembly.Load, LoadFrom, and LoadFile and have tried using all of them. I am still not 100% clear on the difference between Load and LoadFrom. They both seem to automatically load each plug-in's dependencies through fusion and probing from the directory where they are loaded.
My current solution is to search all sub-directories of the AppDomainBase to find and cache all of the DLLs in each plug-in's folder. If I encounter the same assembly more than once, I always keep track of the newest version and its location.
Then, I load each plug-in by calling Assembly.LoadFile so that no dependencies are loaded by fusion. I am subscribing to the AppDomain.CurrentDomain.AssemblyResolve event. When that event is raised, I inspect the Name of the assembly to determine which assembly should be loaded that was pre-cached, and then I load it by calling Assembly.Load.
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
Log(string.Format("Resolving: {0}", args.Name));
// Determine if the assembly is already loaded in the AppDomain. Only the name of the assembly is compared here.
var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().FullName.Split(',')[0] == args.Name.Split(',')[0]);
if (asm == null)
{
// The requsted assembly is not loaded in the current AppDomain
Log(string.Format("Assembly is not loaded in AppDomain: [{0}]", args.Name));
// Determine if the assembly is one that has already been found and cached
var asmName = _RefCandidates.Find(a => a.Name.FullName.Split(',')[0] == args.Name.Split(',')[0]);
if (asmName != null)
{
// The assembly exists in the cache, but has not been loaded. Load it.
Log(string.Format("Pre-loaded assembly found in cache. Loading: [{0}], [{1}]", asmName.Name, asmName.Name.CodeBase));
return Assembly.LoadFile(asmName.File.FullName);
}
}
else
Log(string.Format("Assembly is already loaded in AppDomain: [{0}], [{1}]", asm.GetName(), asm.GetName().CodeBase));
return asm;
}
catch (Exception ex)
{
Logger.Write(ex, LogEntryType.Error);
return null;
}
}
First of all, what is the best way to accomplish what I need to do, and what am I doing wrong?
Second, what happens if a dependency in the chain is expecting to reference something that is in the GAC? I assume it will no longer be found since I am using LoadFile and skipping fusion all together. Also, I have read some things about serialization not working with LoadFile. What specifically? How about resource assemblies?
This model is assuming that all newer versions of dependent assemblies are backward compatible since I'll be loading only the newest version.
Any input is greatly appreciated.
You can't load assembly manually, if the resolution of this assembly not fails(only in this case AssemblyResolve is raised).
So, your plugin architecture can not be implemented with Assembly.Load* methods.
There are two paths now.
First(not recommended): somehow remove the restriction(for example use Assembly.Load at start, and in any point you need an object, search for CurrentDomain assemblies, pick right assembly and by reflection construct objects and use them).
Second(recommended): review your initial problem and search for solution, that can be simple to implement and maintain.
Take a look, what Managed Extensibility Framework offers for plugin architecture.
SharpDevelop folks wrote a book, telling us about their plugins tree.
Find your way.
Also see this link about assembly loading context to understand why Assembly.Load without AssemblyResolve event will not work.

Why FileNotFoundException when I Assembly.Load an assembly that is surely present?

In my Windows Azure role C# code I do the following:
Assembly.Load( "Microsoft.WindowsAzure.ServiceRuntime" );
and FileNotFoundException is thrown. The problem is an assembly with such name is present and has even loaded before the code above is run - I see a corresponding line in the debugger "Output" window and when I do:
AppDomain.CurrentDomain.GetAssemblies().Any(
assembly => assembly.FullName.StartsWith("Microsoft.WindowsAzure.ServiceRuntime"));
the result is true and if I use Where(), then SingleOrDefault() I get a reference to a corresponding Assembly object.
Why can't I load an assembly with Assembly.Load()?
That Load() call can only succeed if Microsoft.WindowsAzure.ServiceRuntime.dll is stored in your app's probing path. By default the same directory as your EXE. Problem is, it isn't stored there, it is stored in the GAC.
The point of the GAC is to act as a depository of assemblies with the same name but different [AssemblyVersion]s, culture or processor architecture. Which is the problem with your Load(), you don't specify any. There is no reasonable way that fusion can pick an assembly for you, it is apt to give you the wrong one. So it doesn't bother, even if there is only one to pick from.
Specifying the full AsssemblyName.FullName is required. Use Project + Add Reference to avoid.
You should load it with a full assembly qualified name.
From the MSDN documentation:
// You must supply a valid fully qualified assembly name.
Assembly SampleAssembly = Assembly.Load
("SampleAssembly, Version=1.0.2004.0, Culture=neutral, PublicKeyToken=8744b20f8da049e3");
The documentation for Assembly.Load says that you're meant to supply the full name for the assembly (including e.g. version information).
Using just a plain name for the assembly will fail if the assembly is normally loaded from e.g. the GAC. E.g.:
try
{
Assembly.Load("System");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.WriteLine(AppDomain.CurrentDomain.GetAssemblies().Any(
assembly => assembly.FullName.StartsWith("System")));
Console.ReadLine();
Exhibits similar behaviour.

Type.GetType fails to create type from already loaded assembly

I have program which loads an assembly using Asssembly.LoadFrom method. Some time later I attempt to use Type.GetType to create a type from that assembly (using AssemblyQualifiedName), but the method returns null. If I set it to throw exception, it tells
Could not load file or assembly '...'
or one of its dependencies. The system
cannot find the file specified.
But I am sure the exact same assembly is already loaded (it shows in the AppDomain.CurrentDomain.GetAssemblies() list).
Anybody has an idea what could be wrong and/or how to solve this issue?
In order to understand why this doesn't work, you need to understand the issue of "load contexts". Type.GetType only looks at the "Load" context. The assembly you loaded into memory was in the "LoadFrom" context.
The only way to really get binds in the load context to see assemblies in the load-from context is to use the AssemblyResolve event and write code to return the correct assembly. The AssemblyResolve event fires right before the bind fails and all other assembly lookup did not succeed.
See the following links for more information about load contexts and issues that arise when using LoadFrom.
MSDN - http://msdn.microsoft.com/en-us/library/dd153782.aspx
AssemblyResolve - http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve.aspx
Suzanne Cook - Link
If you can get the assembly using Assembly.LoadFrom then you can get the type by doing:
Assembly assembly = Assembly.LoadFrom("whatever");
Type myType = assembly.GetType("typeName")
The assembly.GetType has other overloads which you can find out about here

Can't obtain an instance of Type class for a type not defined in the currently executing assembly

How do I obtain an instance of a Type class for a type not defined in the currently executing assembly or in mscorlib.dll?
a) Namely, I've defined a class type someType in assembly CSharpSnapIn.dll, located at E:\CSharpSnapIn.dll, but for some reason when I try to specify an absolute path to this assembly, I get an exception:
Type t = Type.GetType("someType, E:\\CSharpSnapIn.dll"); // exeception
b) I've also tried by putting CSharpSnapIn.dll into \bin\debug directory of a currently running application, but I still get an exception:
Type t = Type.GetType("someType, CSharpSnapIn.dll"); // exeception
thanx
EDIT:
1) I've declared another class type someType2 ( inside CsharpSnapIn.dll )and this time it worked:
Type.GetType("someType2, CSharpSnapIn");
Difference between someType and someType2 is that someType implements an interface declared in external assembly asmIn, but this shouldn't cause an exception, since CsharpSnapIn.dll does have a reference to asmIn?!
2)
Note that the assembly doesn't need to
be loaded first, so long as the
assembly resolver can find it
In other words, calling Type.GetType() first loads an assembly and then creates a Type instance?
3)
The assembly has to be found by
probing, so it would have to be in the
bin directory as per your second
example. If it's an assembly with a
strong name, you have to give all the
details.
So you're saying we can't specify an absolute path ( to an assembly ) using Type.GetType(), but instead assembly needs to reside inside a bin directory?
You will need to first load the assembly:
Type t = Assembly
.LoadFrom(#"e:\CSharpSnapIn.dll")
.GetType("SomeNs.SomeType", true);
You need to give the assembly name - not the file which contains it.
For example:
Type t = Type.GetType("someType, CSharpSnapIn");
The assembly has to be found by probing, so it would have to be in the bin directory as per your second example. If it's an assembly with a strong name, you have to give all the details. Note that someType here also has to be fully qualified in terms of the namespace.
Note that the assembly doesn't need to be loaded first, so long as the assembly resolver can find it. For example, if the assembly is in the same directory as the currently executing assembly, that will be fine in most cases.
As Darin says, an alternative is to load the assembly directly - although in my experience there are quite a few "gotchas" in loading assemblies explicitly, particularly if you've got two assemblies in different locations which both rely on a third assembly. Making sure you only get that third assembly loaded once can be tricky.
You need to choose between LoadFile and LoadFrom etc. Here are some remarks from MSDN:
Use the LoadFile method to load and
examine assemblies that have the same
identity, but are located in different
paths. LoadFile does not load files
into the LoadFrom context, and does
not resolve dependencies using the
load path, as the LoadFrom method
does. LoadFile is useful in this
limited scenario because LoadFrom
cannot be used to load assemblies that
have the same identities but different
paths; it will load only the first
such assembly.

Assembly.ReflectionOnlyLoadFrom not working

I have an Assembly Library1.dll which contains some Interfaces, which were serialized as a byte array into the database. For some reasons we have to change the Interface properties and defintion. so now i am writing a migration utility. So i have 2 versions of Library1.dll , In my utility i have created a folder where i store the new version of Library1.dll . This utility in turn also references Library1.dll hence in bin folder contains Library1.dll but this dll is compiled on older version. My new version of Library1.dll is stored in a private path which i am passing to Assembly.ReflectionOnlyLoadFrom function to instantiate and hence GetTypes on the assembly loaded which further would enable me to do conversion of data.
But I always get ReflectionTypeLoadException when trying to load Library1.dll from private path.
Please help guys!!!. any help would be appreciated. I am really stuck.
Thanks,
AG
If your Library is referencing another dll, GetTypes will fail when it hits a type that uses an external type. Unlike normal assembly loading, ReflectionOnly Assembly loading will not resolve dependencies. You can either subscribe to AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve and load the dependencies as required, or you could pre-load them.
This is the code I use for this:
var assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
foreach (var assemblyName in assembly.GetReferencedAssemblies()) {
try {
Assembly.ReflectionOnlyLoad(assemblyName.FullName);
} catch {
Assembly.ReflectionOnlyLoadFrom(Path.Combine(Path.GetDirectoryName(assemblyPath), assemblyName.Name + ".dll"));
}
}
This will try to load all dependencies of the reflection-only loaded assembly first by fullname, then by path (assuming that the dependency is in the same directory as the loaded assembly).

Categories