Here's a problem which cost me a whole day. I figured I'd post the solution in case anyone else hits it.
I have a .NET Office plug-in which calls an ASMX web service. It gets a valid XML response, but the XmlSerializationReader can't deserialize the response properly. It fails to find one of my DLLs, and throws a FileNotFoundException.
The basic structure is this:
Utility Assembly -- contains some commonly-used utility classes which DTO's depend on.
namespace MyCompany.CommonUtilities
{
[Serializable]
public class UtilityClass
{
// Various fields and properties..
}
}
DTO Assembly -- contains serializable objects for web service calls
using MyCompany.CommonUtilities;
namespace MyCompany.DtoObjects
{
[Serializable]
public class WebResponseDto
{
// Various other fields and properties..
public UtilityClass Property1 { get; set; }
}
}
This all worked great under .NET 3.5, and still does. If I run our old plugin, built against 3.5, everything works.
What happened though, is that in .NET 4.5 they completely reworked the way Xml serializers are generated at runtime. See https://stackoverflow.com/a/14699617/3183464 and the README for .NET 4.5. The end result is that the Xml Serializer can't find the correct DLL for my Utilities project.
When I compile to .NET 4.5 the web service returns a properly serialized object, but it can't be deserialized on the client side. Instead I just get
Exception: There is an error in XML document (1, 997).
System.IO.FileNotFoundException: Could not load file or assembly 'MyCompany.CommonUtilities, Version=6.0.0.23028, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderMyCompanyService.Read101_WebResponseDto(Boolean isNullable, Boolean checkType)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderMyCompanyService.Read258_WebMethodResponse()
at Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer285.Deserialize(XmlSerializationReader reader)
at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
Which was confusing, since the Utilities DLL was already loaded before the webservice was even called.
Looking into the Fusion logs, I saw two distinct load events for the utility assembly. The first was when my Visio plugin first loaded, since it depends on the utility classes:
LOG: DisplayName = MyCompany.CommonUtilities, Version=6.0.0.23028, Culture=neutral, PublicKeyToken=null (Fully-specified)
LOG: Appbase = file:///C:/Program Files (x86)/Microsoft Office/Office14/
Calling assembly : MyCompany.VisioAddIn, Version=6.0.0.23028, Culture=neutral, PublicKeyToken=null.
LOG: This bind starts in LoadFrom load context.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Office/Office14/MyCompany.CommonUtilities.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Office/Office14/MyCompany.CommonUtilities/MyCompany.CommonUtilities.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Office/Office14/MyCompany.CommonUtilities.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Office/Office14/MyCompany.CommonUtilities/MyCompany.CommonUtilities.EXE.
LOG: Attempting download of new URL file:///C:/Users/MyUser/Documents/DevProjects/VisioAddIn/bin/Debug/MyCompany.CommonUtilities.DLL.
LOG: Assembly download was successful. Attempting setup of file: C:\Users\MyUser\Documents\DevProjects\VisioAddIn\bin\Debug\MyCompany.CommonUtilities.dll
Notice the context: It calls Assembly.LoadFrom()
, and it finds the DLL.
But then later, the serialization assembly generated at run-time tries to load the same assembly in order to deserialize a DTO, and can't find it:
LOG: DisplayName = MyCompany.CommonUtilities, Version=6.0.0.23028, Culture=neutral, PublicKeyToken=null (Fully-specified)
LOG: Appbase = file:///C:/Program Files (x86)/Microsoft Office/Office14/
Calling assembly : (Unknown).
LOG: This bind starts in default load context.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Office/Office14/MyCompany.CommonUtilities.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Office/Office14/MyCompany.CommonUtilities/MyCompany.CommonUtilities.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Office/Office14/MyCompany.CommonUtilities.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Office/Office14/MyCompany.CommonUtilities/MyCompany.CommonUtilities.EXE.
LOG: All probing URLs attempted and failed
See what happened?
The assembly generated at runtime doesn't realize it needs to look in the folder where my plug-in lives. It just calls Assembly.Load()
, which uses the default load context.
Since it's running as part of Visio.EXE, Fusion searches the obvious place: C:\Program Files (x86)\Office
, and doesn't find anything. Result: File not found.