Why can I not find that assembly? - c#

AppDomainSetup domainSetup = new AppDomainSetup();
// Assign a global path that includes "prinergyweb\bin"
domainSetup.ApplicationBase = #"E:\Projects\IPP\main\prinergyweb";
domainSetup.PrivateBinPath = #"bin";
AppDomain newDomain = AppDomain.CreateDomain("Test", null, domainSetup);
newDomain.Load("A");
I make sure that all the paths are correct, but it still throws an exception of "FileNotFound"……Why?
How many reasons do you think can cause this problem? (I've made sure "A.dll" exists).
Other information:
PS1: My disc is compacted
PS2: A.dll isn't a strongly-named dll.
PS3: The path is my PerForce's mapping path.

The problem is that with AppDomain.Load assembly is loaded into both domains.
If the current AppDomain object represents application domain A, and the Load method is called from application domain B, the assembly is loaded into both application domains.
The current domain needs to know about those assembly and load them.
If you use the Assembly Binding Log Viewer you will see that the newly created domain loads the assembly from the private bin path, but the current app domain does not.
This is why you get the exception of "File not found".
From MSDN AppDomain.Load Method
This method should be used only to load an assembly into the current
application domain. This method is provided as a convenience for
interoperability callers who cannot call the static Assembly.Load
method. To load assemblies into other application domains, use a
method such as CreateInstanceAndUnwrap.
If you want to load some assembly from a different location you should do like I said in this answer: Assembly loaded from Resources raises Could not load file or assembly and handling the AppDomain.AssemblyResolve Event

Related

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.

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.

Understanding ASP.NET assembly reference management in Web.config file

I have a simple doubt: I have an external assembly that I want to reference. I have an Asp.Net application. I want to use that assembly in my Asp.Net application.
I add a reference and what VS does is just placing my dll in the Bin subdirectory of my web site.
I thought that VS would have modified my web.config file adding external references...
So does it happen only when referencing assemblies in GAC??? (which makes sense given that public tokens and versions are required).
Thankyou
When the CLR loads your assembly for execution, it checks the assembly's manifest to determine what dependencies are required for it to run. It goes through a series of steps to do this:
Check for redirects - if the assembly is strongly-named, the CLR will first check the appropriate config (app.config, web.config, etc.) to see if there are any binding redirects specified. A binding redirect allows the CLR to say, where I am supposed to load v1.0.0.0, instead load v2.0.0.0. If no binding redirect is found for a strongly-named assembly, it will check a policy file in the GAC, and if no policy file is found, it checks the machine.config. If no binding redirect is specified, the CLR will use the assembly name specified in the calling assembly's manifest to load the assembly.
Check to see if the assembly has already been loaded - the CLR determines if the assembly has previously been loaded, if it has, it uses that same loaded assembly, otherwise it continues...
Load the assembly from the GAC - If the assembly could not previously be loaded and is strongly-named, the CLR will attempt to load the assembly from the Global Assembly Cache.
CodeBase - If the CLR still can't find the assembly, it will use the codeBase path to try and locate the assembly.
Probing - If the CLR still can't find the assembly, it will check the probing path for the assembly. The default probing path is the application base path of the AppDomain into which assemblies are currently being loaded.
(That's all adapted from a great article called Understanding .Net Assemblies and References ).
In the case of your web application, the CLR still does all of the above, but the AppDomain application base path is the /bin folder within your IIS application.

Unload AppDomain Assembly

I want to read some information from a .Net assembly, then modify the DLL by appending a short sequence of characters.
The first part works fine, but the second step fails, as the assembly is still in use.
This is the case although I loaded the assembly in its own AppDomain and after I finished step 1 unloaded the AppDomain.
Your best bet is to use something like Cecil that allows you to inspect and rewrite assemblies without loading them in the AppDomain.
References from assemblies loaded into a separate AppDomain have a nasty habit of "leaking" into the parent AppDomain, especially if you're reflecting the assembly and exchanging Type information across the boundary. There are lots of "gotchas" with keeping assemblies isolated in AppDomains truly segregated.
However, there's good news: you probably don't need to worry about the assembly being unloaded in order to modify it on disk - just use shadow copying! Create an AppDomainSetup instance, set its ShadowCopyFiles property to true, and pass it when you create the new AppDomain. This will cause the assembly to be copied to a temporary file before being loaded, keeping the original assembly unlocked and available for modification.
You cannot unload an assembly without unloading the AppDomain:
http://msdn.microsoft.com/en-us/library/ms173101(VS.80).aspx
There is no way to unload an
individual assembly without unloading
all of the application domains that
contain it. Even if the assembly goes
out of scope, the actual assembly file
will remain loaded until all
application domains that contain it
are unloaded.
Are you sure that no other process or AppDomain is using the assembly?

Error from GetSatelliteAssembly called on assembly that lives outside AppDomain paths

The purpose of my application is to pick some localized strings from an assembly. Part of the specification is:
The assembly is to be selected by the user at run-time
The assembly is private one, i.e. not registered with GAC
The code I came up with is:
Assembly resAssembly = Assembly.LoadFile(#"X:\PathToApp\Assembly.Name.dll");
CultureInfo ci = new CultureInfo("es-MX");
Assembly satAssembly = resAssembly.GetSatelliteAssembly(ci);
The last line threw an exception:
Could not load file or assembly "Assembly.Name.resources" or one of its dependencies. The system can not find the file specified.
I have overcome the exception by copying the folders that contain the satellite assemblies to the application root.
I do not like this approach. Any alternative ideas?
Many thanks in advance
The Assembly.LoadFile method doesn't use a binding context, so its dependencies aren't automatically found in its directory.
You can instead use Assembly.LoadFrom.
You can read Suzanne Cook's post on the differences between these two methods to obtain further information (LoadFile vs. LoadFrom).

Categories

Resources