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).
Related
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.
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
I'm using AppDomain.CreateInstanceFromAndUnwrap() to create an object in a different AppDomain. I couldn't get it to work because it kept throwing the following error at me:
Could not load file or assembly 'COMon, Version=2.0.4960.27874, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The module was expected to contain an assembly manifest.
However, I found that it is because it tries to load my DLL (which has the same name as my .NET assembly).
This is how I call the method:
_script = (Script)_appDomain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, "COMon.Scripting.Script");
It works fine as long as there isn't a native DLL file with the same name as my .NET assembly. Why does this happen when I'm passing it the full path and filename of my .NET assembly?
when I'm passing it the full path and filename of my .NET assembly?
That's not how the method works. The first argument is the display name of the assembly. It is not a file name. The MSDN article recommends that you take a look at Assembly.FullName to learn more about display names.
So the normal CLR search rules will be in effect for finding the assembly. It will look in the GAC first, then in the probing path for the AppDomain. With a quirk that you didn't count on, the CLR does not pay attention to the filename extension for a file. The display name for an assembly doesn't specify it. So it considers an EXE and a DLL equivalent. Something you can see back in the trace for Fuslogvw.exe, the utility you always want to use when you have trouble like this. And in other places, adding a reference to an EXE works fine for example.
So it finds COMon.exe and that's a kaboom, it is not a managed assembly.
It isn't that clear what the proper workaround might be in your case, other than simply renaming the assembly. When you tinker with AppDomains then you typically also want to use AppDomainSetup and set the ApplicationBase or PrivateBinPath property.
I am writing an application which uses plugins. Plugins are class libraries which lie in Plug-ins directory. My app loads these libraries via LoadFrom. Some of them have dependencies in the form of libraries which lie in the same Plug-ins directory. When I try to create instance of class from one of plugins via Activator.CreateInstance i recieve an exception 'Unable to find assembly' (this is dependency assembly of plugin), but this assembly is already loaded (!) along with plugins and It is visible in ProcessExplorer.
I can't uderstand in what my trouble is.
Your problem might be, that de loaded assembly isn't the same version as the request one. .Net Runtime maps the Assembly after their name and after their Version if the name equals and the Version differes you get an exception if the other one is loaded, which says "Assembly cann't be found" or something like that. The Problem is, that the assembly could not be matched properly. But there is a solution:
Take a look at the MSDN for further information about that Problem.
Solution for that problem:
If you have to load 2 Versions of that assembly try helping the runtime by implementing the AssemblyResolve Event Samples are also here.
Try using the AssemblyBindLogViewer to determine the dependencies of your plugins and to crosscheck your problem.
I recommend implementing the event anyways if you deal with plugins,
so you can log all assembly requests of that AppDomain.
You will find furhter information about runtime behavior and assembly loading here
Hope i could help, please give us feedback about your solution!
Configuring the Plugin Folder
Load the Plugins into a seperat AppDomain which has the pluginfolder as ApplicationBaseTo configure AppDomains see. This is the recomendet solution to load Plugins, becaus you can define the security level of the AppDomain (Sandboxing)
Extend your current AppDomains PrivatePath, so it also searches the Assemblies in this Path. This method is Obsolete!(but does it's job)
You should provide Full Path of assembly files.
class Program
{
static void Main(string[] args)
{
var asmFileName = "test.dll"; // Your plug-in file name
var asmPath = AppDomain.CurrentDomain.BaseDirectory; // Your assemblies's root folder
var asmFullPath = System.IO.Path.Combine(asmPath, asmFileName);
var asm = System.Reflection.Assembly.LoadFrom(asmFullPath);
}
}
I had similar problems and they were usually solved by changing target framework in the project properties...
I am loading an assembly X.dll in my program, where X.dll can be anything, and I create an instance of the class X.A_Class. But what if the assembly X requires assemblies A, B, C and D?
How do I detect this?
How do I load them without holding them in a variable?
You can use Assembly.GetReferencedAssemblies as #alexn mentioned, and then use Assembly.Load to load them. Alternatively, you can hook AppDomain.CurrentDomain.AssemblyResolve and load them on demand.
If you do need to iterate them, make sure you do it recursively to get transitive dependencies.
You can get the referenced assemblies for an assembly with the Assembly.GetReferencedAssemblies method.
Refferenced assemblies normally will be loaded automatically (see related messages like How is an assembly resolved in .NET? for starting links).
If you are loading assemblies not from standard locations (like GAC and application's root folder) you may need either setup path to load referenced assemblies from (search for "assembly default load path" - i.e. app config file - http://msdn.microsoft.com/en-us/library/823z9h8w.aspx) or load them yourself from AssemblyReslove event as mentioned in other answers.
The best way to start debugging the issues with loading assemblies is read blogs at: http://blogs.msdn.com/b/suzcook/archive/2003/05/29/57120.aspx (and related posts http://blogs.msdn.com/b/suzcook/archive/tags/loader+debugging+advice/ )
EDIT: folder -> "root folder" + link to config file topic for probing paths.