Odd behavior when attempting to get a Type by AssemblyQualifiedName at runtime

4

I'm getting a FileLoadException when attempting to deserialize a type using the NetDataContractSerializer:

The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)

This error does not have to do with the serializer specifically; attempting to load a type at runtime by its Assembly Qualified Name results in the same failure.

I've attached a listener to the AssemblyResolve event to see what's happening:

ResolveEventHandler reh = (o, e) =>
{
    var tryGet = AppDomain.CurrentDomain.GetAssemblies()
                          .Where(x => x.FullName == e.Name).FirstOrDefault();
    if (tryGet != null)
        return tryGet;
    //EDIT:  Crap, the following line is a stupid bug STUPID!  Ignore!
    return Type.GetType(e.Name).Assembly;
};

using (var stream = System.IO.File.OpenRead(serializedObjectFilename))
{
    try
    {
        AppDomain.CurrentDomain.AssemblyResolve += reh;
        var ser = new NetDataContractSerializer();
        return ser.Deserialize(stream) as MyType;
    }
    finally
    {
        AppDomain.CurrentDomain.AssemblyResolve -= reh;
    }
}

The titular "odd behavior" can be seen by debugging through the handler. While tryGet is never null (the required assembly, in this case, is always loaded into the AppDomain), the operation always fails if left to itself. In other words, calling Type.GetType(e.Name).Assembly would result in the FileLoadException being thrown. Edit: I was conflating the strong name of the assembly with the assembly qualified name of a type; please ignore that bug. Ironically, it doesn't throw a different error, so I didn't catch this before asking this question.

Another bit of information: Assembly.Load(e.Name) always returns the valid assembly. I'm not sure why that works whereas the method used behind the scenes during deserialization fails.

Fusion Log reports that the loader is attempting to load the correct assembly, but since the assembly isn't found within the private path of the executable, it fails.

Why is the attempt to load the assembly being made when the assembly is already loaded in the AppDomain??


More details on fusion logging...

I've captured all assembly loading before and during the method call that causes the exception to be thrown. Here are the relevant logs, in order of creation:

  • Partial binding FAILED
    • Attempted to load the assembly by name only
    • Only probed the app base
  • Partial binding via LoadFrom SUCCEEDED
    • Where-ref bind. Location points to where the file is referenced
    • I believe VS uses LoadFrom when loading references in a solution
  • Strong name binding FAILED
    • Unknown what triggered this attempt to load
    • Only probed the app base

AFAICT, Visual Studio loads the assembly into the solution's AppDomain (ffs I wish Fusion Log captured the AppDomain where the load was attempted; it records the calling assembly, after all).

After this point, I make my deserialization call. The result is a single log in Fusion:

Bind result: hr = 0x80070002. The system cannot find the file specified.

Again, Fusion is attempting to load from the executable's application base by its strong name. One good thing; it attempts to load from the GAC, so once deployed I may not have the same issue. But I'm still clueless as to why the assembly cannot be located in the appdomain.


Further interesting stuff...

This throws on the call to Deserialize:

MyType test = new MyType ();
var serialized = Serializer.ToXml(test);
// the following line fails with a FileLoadException
var deserialized = Serializer.FromXml<MyType>(serialized);

where ToXml and FromXml both use the NetDataContractSerializer and Write/ReadObject. The assembly is loaded early in execution from the package's installation directory, but the NDCS, for some reason, doesn't want to use the assembly as it is found in the AppDomain. This test shows that it can't be an issue with versioning.

.net
assembly-resolution
fileloadexception
asked on Stack Overflow May 17, 2011 by Will • edited May 17, 2011 by Will

1 Answer

0

One thing I find odd in your code is that here:

GetAssemblies().Where(x => x.FullName == e.Name)

e is used as the name of an assembly, since it would match Assembly.Name, so would not have the name of a class/type in it, then here:

return Type.GetType(e.Name).Assembly;

e is used as a fully assembly qualified type name, which I would think would contain both the class/type name plus the assembly name.

Is this intentional?


Edit:

Sorry, I posted this reply just as you edited your post and caught the mistake yourself...

answered on Stack Overflow May 17, 2011 by CodingWithSpike

User contributions licensed under CC BY-SA 3.0