13

I have this code:

public byte[] SerializeToBlob()
{
    using (var buffer = new MemoryStream())
    {
        var formatter = new BinaryFormatter();
        formatter.Serialize(buffer, this);
        buffer.Position = 0;
        return buffer.ToArray();
    }
}

public static ActionData DeserializeFromBlob(byte[] state)
{
    using (var buffer = new MemoryStream(state))
    {
        var formatter = new BinaryFormatter();
        var result = formatter.Deserialize(buffer);
        return (ActionData) result;
    }
}

And am calling it as follows:

byte[] actionDataBlob = ad.SerializeToBlob();
var ad1 = ActionData.DeserializeFromBlob(actionDataBlob);

However, I get an InvalidCastException when it tries to cast the deserialized object to its type:

[A]ActionData cannot be cast to [B]ActionData. Type A originates from 'XXXX.XXXX.Auditing, Version=1.0.76.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\Craig\AppData\Local\Temp\Temporary ASP.NET Files\root\5d978e5b\ffc57fe1\assembly\dl3\2b1e5f8f\102c846e_9506ca01\XXXX.XXXX.Auditing.DLL'. Type B originates from 'XXXX.XXXX.Auditing, Version=1.0.76.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' at location 'F:\Visual Studio Projects\XXXXXXXXX\source\XXXX.XXXX.SilverlightClient.Web\bin\XXXX.XXXX.Auditing.dll'.

(XXXX.XXXX is there to obscure the client's name)

What gives?

I've now asked a related question here:

How should I serialize some simple auditing data for storing in a SQL table?

Community
  • 1
  • 1
Craig Shearer
  • 14,222
  • 19
  • 64
  • 95

6 Answers6

4

It sounds to me like you have the same class in different assemblies (or web applications). BinaryFormatter includes the type metadata in the serialization, which means that only the exact same assembly will do. 2 solutions:

  • put this type in a dll, and reference that single dll in both places
  • use a contract-based serializer

Personally I would choose the second for a huge number of reasons not just limited to this one. Likely choices:

  • XmlSerializer (xml; serializes public fields and properties; "tree" only)
  • DataContractSerializer (xml; serializes marked fields and properties (public or private); "tree" or "graph")
  • protobuf-net (binary; serializes marked fields and properties (public or private); "tree" only)

Which is best depends on the scenario.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • OK, thanks for the advice on other options. I'm not sure which to choose anyway - actually I'll formulate another question. BUT, as I said in a previous comment, those two lines of code are next to each other, IN THE SAME ASSEMBLY! – Craig Shearer Jul 17 '09 at 07:31
  • In which case, the first approach (moving this object into a dll) might be the most pragmatic. – Marc Gravell Jul 17 '09 at 07:33
  • In this case, it's the same assembly because the full assembly name is the same. But I second XmlSerializer etc. in favour of binary serialization. – Anton Tykhyy Jul 17 '09 at 07:37
  • Binary serialization isn't the problem; BinaryFormatter is ;-p There is a subtle difference – Marc Gravell Jul 17 '09 at 08:10
4

You have loaded the same assembly twice, in different loader contexts. E.g. you happened to load the XXX.Auditing with Assembly.LoadFrom() first, and then some other (or your) assembly loaded it normally. In fact, the binary deserializer could be the one who loaded the assembly a second time, though I wouldn't know why (no experience with ASP.NET).

Anton Tykhyy
  • 19,370
  • 5
  • 54
  • 56
  • Ah, well actuallly that makes some sense. The Auditing DLL is actually loaded by an ORM dynamically - it's probably using Assembly.LoadFrom() to load it. But that's the assembly that is using the BinaryFormatter to deserialize it, so I don't understand why it would be being loaded again. But then, I don't have much experience with using the BinaryFormatter, so I certainly couldn't be sure. – Craig Shearer Jul 17 '09 at 07:46
  • Then there must be someone else who loads this assembly into the 'Default' context. You could try hooking some assembly load events to find out who loads what. – Anton Tykhyy Jul 17 '09 at 09:26
2

Ok. I have just run into this same problem.

In my case, the issue was because i was loading an assembly from a byte array (i'm using a plugin model, so this is fairly common use) and was deserializing an object but it wouldn't cast with the same message as the original question.

At first i thought it was just because of the serializer and dll versioning and such...so i wrote my own serializer and ran into the same issue again.

The issue really came from type creation. In my deserialization routines i was using the familiar Type.GetType(string) method and passing an AssemblyQualifiedName, which does work and for all types that reside outside of mscorlib, it's required. Well, turns out that GetType does not walk the list of loaded assemblies to try and find a match but leaves it up to the fusion resolver.

This means that any type that exists in an assembly that was loaded in any context other than 'Load' (aka 'Default' in the exception message) is not found and GetType attempts to load the assembly normally.

In my case, this behavior caused 2 instances of the assembly to be loaded in the appdomain: on from my byte array, and the other from disk as found by fusion.

I solved this problem by enumerating the loaded assemblies in my appdomain, looking for a matching assembly name (parsed from my AssemblyQualifiedName). Once found, i created my type (minus the assembly info) using that specific assembly (Assembly.GetType(String)).

Here's the code that allowed me to overcome this issue:

Dim ot As System.Type
Dim tname = "MyType"
Dim aname = "MyPlugin"
'// The following lambda expression returns only assemblies that match my assembly name
'// Your assembly name could be a fully qualified name or just the simple assembly name
'// I chose to use the simple name so that i didn't have to store unnecessary data and because i didn't want version specific info

Dim asms = AppDomain.CurrentDomain.GetAssemblies().Where(Function(__) __.GetName.Name.Equals(aname))
'If there is only one assembly loaded...use it
If asms.Count = 1 Then
        ot = asms(0).GetType(tname)

'// If there are multiple assemblies loaded (with the same name), i'm picking the one that is loaded from disk, if such is available, otherwise default back to the first one that was loaded into the appdomain
'// If you do have multiple assemblies loaded, it's because you (or .NET) has loaded them in different contexts.  You might need to adjust for which context you want.  I suppose you could pass the desired context in as a parameter and look for it
ElseIf asms.Count > 1 Then
    Dim asm = asms.FirstOrDefault(Function(__) Not String.IsNullOrEmpty(__.Location))
    If asm IsNot Nothing Then
        ot = asm.GetType(tname)
    Else
        ot = asms(0).GetType(tname)
    End If
Else
    '// not yet loaded...use default type resolution from Type.GetType
    ot = Type.GetType(tname & "," & aname)
End If

Dim obj
'// Note that the method here is using the already resolved System.Type from above.
'// This is important because it causes Activator to create an instance from the assembly
'// that we really want and not one from fusion's resolver.
obj = Activator.CreateInstance(ot)

Hopefully this is helpful to someone else.

-Eriq

iQueue
  • 191
  • 1
  • 6
1

In the end, my problem was with the dynamic loading, I think. When I implemented it using the XmlSerializer I had exactly the same problem.

The solution was to put the classes I wanted to serialize in a separate assembly so they weren't dynamically loaded.

Craig Shearer
  • 14,222
  • 19
  • 64
  • 95
0

I am having this same problem with an Microsoft Office InfoPath form using .net code. Even Microsofts own code has tripped up on this issue.

Yes I can see it is loading from two different locations at the same time.. \Appdata\Local\assembly\dl3...

and also

\AppData\Local\Micorosoft\InfoPath\FormCache4...

sigh

hamish
  • 1,141
  • 1
  • 12
  • 21
0

Im having the same problem and exactly the same error, but not everything, but occasionally. im using linqtoSQL and the data list is serialized and then access via

filteredTasks = (List<App.Task.Entity.GetAllTasksResult>)Session["myTaskList"];
visual
  • 305
  • 4
  • 11