views:

253

answers:

3

In .NET 2.0 (and upwards, I presume), Version Tolerant Serialization will succesfully deserialize a serialized object from an older version of the assembly in which the object resides.

When I open such a binary formatted serialized stream using a hex viewer (a simple drag'ndrop into VS will do) I can see there's assembly information contained in this stream.

Is there, during deserialization, a way to retrieve this information? This can be used, for example, to apply fixups to known issues when reading in older content.

UPDATE: It looks like it can't be done (apart from changing the class itself, as in Paul Betts answer, didn't test that either) so is there any other way to read this value? Is the binary format published?

+1  A: 

Add a field to all your serialized classes called AssemblyInfo that gets set to Assembly.GetExecutingAssembly().FullName

Paul Betts
A: 

Use Lutz Roeders (now Red Gate's) Reflector.

the System.Runtime.Serialization.Formatters.Binary.__BinaryParser class is used internally by the BinaryFormatter's Deserialize method to do the actual parsing.

Poking around in reflector might give you ideas on how to pre-read the binary header and determine version info.

John Weldon
+3  A: 

I discovered these serialization issues first-hand while writing this CodeProject article (scroll to 'Loading the Catalog from Disk', about half-way down).

Basically I was serializing something with an ASP.NET application - and the serialized data could not be read after the IIS Application was restarted (due to the whole dynamic compilation/temporary assembly cache/etc that ASP.NET does)! Ouch!

Anyway, my first point is the Exception thrown during deserialization includes the strong name

Cannot find the assembly h4octhiw, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

so obviously you are correct that the information you want is in there "somewhere". Theoretically (and yes, this is a TERRIBLE idea) you could catch serialization exceptions and parse the error for the older version details (of course 'current' deserialization will work without throwing)... BUT there might also be a better way...

The second point relates to the solution I implemented (using this info). I wrote a custom System.Runtime.Serialization.SerializationBinder: the is shown code below as an example.

public class CatalogBinder: System.Runtime.Serialization.SerializationBinder
{
    public override Type BindToType (string assemblyName, string typeName) 
    { 
        // get the 'fully qualified (ie inc namespace) type name' into an array
        string[] typeInfo = typeName.Split('.');
        // because the last item is the class name, which we're going to 
        // 'look for' in *this* namespace/assembly
        string className=typeInfo[typeInfo.Length -1];
        if (className.Equals("Catalog"))
        {
            return typeof (Catalog);
        }
        else if (className.Equals("Word"))
        {
            return typeof (Word);
        }
        if (className.Equals("File"))
        {
            return typeof (File);
        }
        else
        {    // pass back exactly what was passed in!
            return Type.GetType(string.Format( "{0}, {1}", typeName, 
                                assemblyName));
        }
    } 
}

Basically BindToType is being given the opportunity by the deserialization process to 'substitute' a known Type for the one originally used to serialize THAT OBJECT. I am only using the typeName, but the assemblyName probably contains the information you are after, AND a custom SerializationBinder is probably the method you should investigate to 'use' it.

FYI, the code above was 'wired up' like this:

System.Runtime.Serialization.IFormatter formatter = 
    new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
formatter.Binder = new CatalogBinder(); // THIS IS THE IMPORTANT BIT
object deserializedObject = formatter.Deserialize(stream);
CraigD
This is exactly what I was looking for.
Dave Van den Eynde