C# Assembly.Load with reference dlls - c#

I am using the following code to load dlls at run time, and store their classes for later use.
public LoadDll(byte[] data)
{
Assembly loadedAssembly = Assembly.Load(data);
System.Type[] types = loadedAssembly.GetTypes();
TypeRepo.Register(types);
}
and this works great, but if the dll I built has reference to another dll I get the error "The classes in the module cannot be loaded." when calling GetTypes().
How can I provide a specific file path to allow the loaded assembly accesses to a dependency on disk?

You should play with AppDomain.AssemblyResolve event
See what remarks section of the linked documentation points out:
It is the responsibility of the ResolveEventHandler for this event to
return the assembly that is specified by the ResolveEventArgs.Name
property, or to return null if the assembly is not recognized. The
assembly must be loaded into an execution context; if it is loaded
into the reflection-only context, the load that caused this event to
be raised fails.
So you need to perform an Assembly.LoadFrom to return the whole Assembly instance by loading satellite assemblies from an arbitrary path defined by you in code.

Related

Why is AssemblyResolve event trying to resolve an assembly that is already loaded in the AppDomain?

I have a service that involves downloading an assembly from cloud storage, creating an instance of it using Activator.CreateInstance and then invoking a method on it.
I have set up a AssemblyResolve method to download dependencies which works fine, but to test/experiment I am now trying to manually download assemblies. I have got as far as finding which dependencies are needed, downloading them and then loading them using
Assembly.Load(byte[])
After which I can see they are loaded into the AppDomain via
AppDomain.CurrentDomain.GetAssemblies())
However when I am invoking the method on the assembly which references this, it still goes to the AssemblyResolver.
I may be misunderstanding how loaded assemblies and the AppDomain works but it seems to me that once the assembly is loaded it should be available to this assembly and it shouldn't need to resolve it?
Why can't it "see" it? The version and name etc is the same.
I have read about the different assembly binding contexts here and I think this could be the issue? It suggests that using Assembly.Load(string) will load to a different context than Assembly.Load(byte)? In which case how do I do this when I just have the assembly in memory as a byte[]?
Thanks
You need to obtain the Type directly from the assembly you loaded, as it is not loaded into the correct context.
var assembly = Assembly.Load(File.ReadAllBytes(some_path));
// This will work. Note that you don't need the assembly-qualified name,
// as you are asking the assembly directly for the type.
var type1 = assembly.GetType("My.Special.Type");
// This will not work - the assembly "My.Assembly" is not loaded into
// the Load context, so the type is not available.
var type2 = Type.GetType("My.Special.Type, My.Assembly");
In the code above, type1 will have a reference to the Type, but type2 will be null, as the assembly is not loaded into the normal Load context.

Reflection - trouble with loading dependent assembly

I am trying to execute below code to see if assembly was built in Debug or Release mode.
Assembly assemb = Assembly.LoadFile(fileName);
bool isDebug = false;
foreach (object att in assemb.GetCustomAttributes(false))
if (att is DebuggableAttribute)
isDebug = ((DebuggableAttribute)att).IsJITTrackingEnabled;
Console.WriteLine("Assembly is {0}.", isDebug ? "debug" : "release");
I am able to load assembly (Product.dll) without any issue. But when I am trying to execute GEtCustomAttributes(false) method I am getting below exception message.
Could not load file or assembly 'log4net, Version=1.2.11.0,
Culture=neutral, PublicKeyToken=null' or one of its dependencies. The
system cannot find the file specified.
Don't know why this is looking for dependent assembly. Is it because of the way Product.dll was build ( like optimization or something ). I don't have access to source code of Product.dll so not sure how I can file its mode ( Debug or Release )
Typically dependent DLLs are not compiled into the resulting DLL. This means that if Product.dll is compiled with a dependency to log4net.dll, it has to be in the same folder.
It should be possible to simply copy log4net.dll into the same folder where Product.dll is located.
The dependency must be loaded if a type defined in Product.dll references a type defined in log4net.dll. In this case I strongly suspect that it will be Logger/ILog, because this line is often included to get the Logger.
private static readonly log4net.ILog log = log4net.LogManager.GetLogger();
If log4net is a mandatory (meaning directly referenced) assembly for your referenced project you have to also load it, there is no way to only load parts of an assembly. Having said this the code for the referenced assembly won´t probably won´t even compile if log4net.dll is missing so it HAS to load it in any way.
Anyway obiosly any of your attributes defined in the referenced assembly needs the Logger from log4net, so it´ll search for that type.
Put the log4net assembly into your build-path and it should work.

Loading Dependent Assemblies Manually

I have a project that loads multiple versions of the same assembly using either Assembly.Load or Assembly.LoadFile. I then use Assembly.CreateInstance to create a type from that specific assembly.
This works great until the type I'm creating references another dependent assembly. I need a way to intercept this specific assembly's request to load another assembly and provide it with the correct version (or, even better, probing path) to look for its dependency.
This is required because v1 and v2 of the assemblies I'm creating with Assembly.CreateInstance will often need different versions of their dependent assemblies as well, but both v1 and v2 will, by default, probe the same directories.
I've seen examples of how to do generally for an AppDomain, but I need to do this in a way that handles all resolution from a particular root assembly. Assuming I do something like:
AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs args)
{
//Use args.RequestingAssembly to determine if this is v1 or v2 based on path or whatever
//Load correct dependent assembly for args.RequestinAssembly
Console.WriteLine(args.Name);
return null;
};
This may work for dependencies immediately referenced by my target assembly, but what about the assemblies that those dependencies reference? If v1 references Depv1 which itself references DepDepv1, I'll need to be able to know this so I can ensure it can find them properly.
In that case, I supposed I would need to track this somehow. Perhaps by adding custom assembly evidence - although I haven't been able to get that to work, and there doesn't appear to be any "assembly meta data" property that I can add to at runtime.
It would be far, far easier if I could simply instruct a particular assembly to load all its dependencies from a particular directory.
Update
I managed to use the AssemblyResolve event to load the dependent assemblies based on the path of the RequestingAssembly, but it seems to be a flawed approach. It seems as though the which dependent assembly version while be used is entirely dependent on which version happens to be loaded first.
For instance:
Load v1
Load v2
Reference v1 causes load of Depv1
Reference v2 causes load of Depv2
Code in v1 uses type from Depv1 (Works)
Code in v2 uses type from Depv2 <-- fails because it gets type from Depv1!
I'm only inferring steps 5 and 6 at this point, but I do see Depv1 AND Depv2 being loaded.
As it turns out, the key to making this work is to ensure you use Assembly.LoadFile. LoadFile is the only method that will load an assembly even if it matches an assembly that .NET thinks is already loaded. I discovered this from an article on codeproject.
Since I needed to load two different assemblies that both had identical full names (i.e. "App.Test.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null") but had different contents, LoadFile was the only way to accomplish this. My initial attempts used the Load overload that accepted the type AssemblyName, but it would ignore the path defined in the AssemblyName instance and instead return the already loaded type.
To force an entire dependency graph to load from a specific location regardless of what other types are already loaded is to register for the AssemblyResolve event:
AppDomain.CurrentDomain.AssemblyResolve += ResolveDependentAssembly;
And ensure that we use LoadFile to load the dependency:
private Assembly ResolveDependentAssembly(object sender, ResolveEventArgs args)
{
var requestingAssemblyLocation = args.RequestingAssembly.Location;
if (thePathMatchesSomeRuleSoIKnowThisIsWhatIWantToIntercept)
{
var assemblyName = new AssemblyName(args.Name);
string targetPath = Path.Combine(Path.GetDirectoryName(requestingAssemblyLocation), string.Format("{0}.dll", assemblyName.Name));
assemblyName.CodeBase = targetPath; //This alone won't force the assembly to load from here!
//We have to use LoadFile here, otherwise we won't load a differing
//version, regardless of the codebase because only LoadFile
//will actually load a *new* assembly if it's at a different path
//See: http://msdn.microsoft.com/en-us/library/b61s44e8(v=vs.110).aspx
return Assembly.LoadFile(assemblyName.CodeBase);
}
return null;
}
Yes, this code assumes that if your root assembly has dependencies, that they're all located at the same path. That's a limitation, no doubt, but you could fairly easily add additional hints for non-local dependencies. This also would only be an issue if the already loaded version of those additional dependencies wouldn't work.
Lastly, none of this would be necessary if the assembly versions were properly incremented. The Load call would not treat an already loaded Depv1 as the same as a request Depv2. In my case, that wasn't something I was willing to deal with as part of my continuous integration and deployment process.
Try Assembly.LoadFrom(path); which will resolve dependencies automatically.

Why do I need an AssemblyResolve handler for an assembly which is already loaded?

I have two assemblies: App and AddOn. App references AddOn, but CopyLocal is set to false, since AddOn will be loaded dynamically by App.
Here is the code in AddOn:
namespace AddOn
{
public class AddOnClass
{
public static void DoAddOnStuff()
{
Console.WriteLine("AddOn is doing stuff.");
}
}
}
and here is the code in App:
class Program
{
static void Main(string[] args)
{
Assembly.LoadFrom(#"..\..\..\AddOn\bin\Debug\AddOn.dll");
// Without this event handler, we get a FileNotFoundException.
// AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
// {
// return AppDomain.CurrentDomain.GetAssemblies()
// .FirstOrDefault(a => a.FullName == e.Name);
//};
CallAddOn();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void CallAddOn()
{
AddOnClass.DoAddOnStuff();
}
}
What I don't understand is why the code doesn't work with the AssemblyResolve handler commented in Main(). When run in Visual Studio, the debugger breaks on CallAddOn() with a FileNotFoundException. Why is it complaining? The assembly is loaded, and it's the exact same version (i.e. same file on disk) as what was referenced by App.
I feel like there is some fundamental concept that I'm not understanding properly here. The commented AssemblyResolve handler works fine, but it seems like a hack and I don't understand why I need it because it seems like it's doing something trivial.
The reason is that there are multiple assembly loading contexts. The context which an assembly is loaded into affects how it can be used. When an assembly is loaded by the runtime using the default probing mechanism it is put into the so-called Load context. This is the context used when you load an assembly via Assembly.Load. You have loaded the assembly using LoadFrom which uses its own context. Probing does not examine the LoadFrom context and the file is not in the probing path so you are required to resolve it for the runtime. This is not symmetric however. If an assembly is loaded in the Load context, LoadFrom will load it from there first (assuming the identity is the same. for unsigned assemblies, the path is part of the identity.). I will note there are more contexts including ReflectionOnlyLoad and ReflectionOnlyLoadFrom. LoadFile loads an assembly without a context, i.e. all dependencies must be manually loaded.
If you want assemblies to be resolved in the Load context, but have them exist outside the application's default probing path you can do it via configuration as well. Use either the <codebase> element of an assembly binding redirect or the privatePath attribute of the <probing> element.
Read this for more information. There are also some blog posts by Suzanne Cook from a while back on assembly loading and contexts (see here, here, and here).

Dynamically loading multiple versions of an assembly

I am writing a test app to perform some regression tests. The idea is to be able to run test over multiple versions of a library. My goal is to load the dlls up in a Dictionary where the key is the version string (such as "3.0.0.0") and the value is the Assembly instance. I am able to dynamically load one assembly and call a method on it, but when I try to load a second one, I get the following exception:
The located assembly's manifest definition does not match the assembly reference.
I am loading the assemblies with the following line:
asm = Assembly.LoadFrom(lib, hash, System.Configuration.Assemblies.AssemblyHashAlgorithm.MD5);
'lib' is the complete filename and path of the dll.
'hash' is the md5 sum of the dll.
I looks like even though I am telling Windows "use this dll", it looks at the name and says "I already have that one" and uses the previously loaded one and since the hash doesn't match, it fails. Originally, the dlls being loaded did not have an Assembly Version set, so I set it on 4 different versions, but it still threw the same exception.
What is the fix for this?
Jordon
You cannot load more than one version of the same assembly into single AppDomain. Also once loaded, assembly cannot be unloaded from AppDomain (with exception of dynamically created transient assemblies in .NET 4), but it is possible to unload whole AppDomain (which unloads all assemblies, that were loaded in it). Therefore you must load each version of your assembly into separate (newly created) AppDomain. Also be very careful to NOT pass any reference to loaded assembly between individual AppDomains (and especially to main AppDomain, where your testing app resides), because otherwise .NET will try to load assembly into every AppDomain, where reference to this assembly appears and you will end up with the same error again.
You'll need to the assemblies into separate AppDomains.

Categories

Resources