Following the example of the following KB article we have implemented the AssemblyResolve event in order to point .Net to the correct folder to load assemblies from: http://support.microsoft.com/kb/837908
This is our current implementation:
public static class AssemblyLoader
{
/// <summary>
/// A custom AssemblyResolver that will search for missing assemblies in the root and subfolders of the executing assembly
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
/// <returns></returns>
public static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string assemblyPath = string.Empty;
string assemblyFileName = string.Empty;
try
{
// This handler is called only when the common language runtime tries to bind to the assembly and fails.
string rootProbingPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
// Loop through the referenced assembly names.
assemblyFileName = args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
// Search for the filename in the root and subfolders of the rootProbingPath
string[] matchingAssemblies = Directory.GetFiles(rootProbingPath, assemblyFileName, SearchOption.AllDirectories);
// If a match is found, awesomeness was achieved!
if (matchingAssemblies.Length > 0)
assemblyPath = matchingAssemblies[0];
// Throw a clear exception when the assembly could not be found.
if (string.IsNullOrEmpty(assemblyPath))
throw new FileNotFoundException(string.Format("AssemblyLoader: Could not find assembly '{0}' in '{1}' or its subfolders.", assemblyFileName, rootProbingPath));
Console.WriteLine(string.Format("[" + DateTime.Now.ToString() + "] AssemblyLoader: Assembly '{0}' found at '{1}'", assemblyFileName, assemblyPath));
// Load the assembly from the specified path.
return Assembly.LoadFrom(assemblyPath, AppDomain.CurrentDomain.Evidence);
}
catch (Exception ex)
{
throw new Exception(string.Format("[" + DateTime.Now.ToString() + "] The assemblyloader could not load the assembly '{0}' from path '{1}': " + ex.Message, assemblyFileName, assemblyPath), ex);
}
}
}
We use this in all of our batch procedures which often run in parallel and using mostly the same assemblies.
Periodically a batch will crash leaving the following trail:
[26/04/2013 12:35:01] AssemblyLoader: Assembly 'COMPANY.DistributieOrderFacade.dll' found at 'C:\COMPANY_Batch\MDS\Executables\MDS\FACADE\COMPANY.DistributieOrderFacade.dll'
[26/04/2013 12:35:01] AssemblyLoader: Assembly 'COMPANY.DOCUMENTCENTERDOCS.dll' found at 'C:\COMPANY_Batch\MDS\Executables\MDS\CLIENT\COMPANY.DOCUMENTCENTERDOCS.dll'
26/04/2013 12:35:01: Create COMPANYDocument...in queue:\rug.adroot\dfsroot\mds\Data\queue_new\ 26/04/2013 12:35:01
Could not load file or assembly 'COMPANY.DistributieOrderBRDA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. General Exception (Exception from HRESULT: 0x80131500)
The assembly in the error is indeed located in one of the folders which the AssemblyLoader searches. And if I run the procedure again, it does succeed.
I've tried to see if the dll's are getting locked by using this assembly loader in two console apps and access the same dll's at the same time, but this did not seem to be a problem.
I should also mention that this is just a log from one of the batches. There are others, and it's rarely the same assembly that is not being loaded.
I don't know where to start looking for a solution next.
It was due to an update script that updated the dll's before each batch was running. When two batches were running at the same and there was a dll that needed updating, locks were occurring on the dll's which was causing the problem.
We've removed this script for a week now and haven't seen any problems yet. So our conclusion is that this was the problem.