Geeks With Blogs
Gavin Stevens's Blog the ramblings of another developer....

I was recently working with binary serialization of particular objects, and ran into quite a problem that I though i would share the solution to.

Problem: After serializing a class to a binary file, I was unable to read the file in future versions of release assemblies because of version changes and Assembly name changes.  We have an existing shipping product that stores it state in a configuration file.  The currently shipping products assembly was named Config. The assembly was not signed and the version of the assembly was just based off its build number.  We needed to make some major changes to the architecture of the application by completely renaming the object, signing it, and including it in the GAC.  The problem arose when shipping this new Gac'd assembly.  We wanted it to read the old configuration file.  The CLR upon trying to deserialize a serialized type will attempt by default to load the old type that the file was serialized with.  example, the header on the original configuration file was: Config, Version=1.0.1672.18504, Culture=neutral, PublicKeyToken=null while the new assembly would save files with a header Namespace.Assembly.Object, Version=7.3.54.0 PublicKeyToken=xxxxxx; So, when trying to read the serialized type, the CLR would look for a version of the Config assembly on the machine, of course not finding it and would throw a Serialization Exception or an Invalid Cast Exception trying to load the type.

Here is the workaround:

Define a class as follows;

sealed class VersionConfigToNamespaceAssemblyObjectBinder : SerializationBinder

{

public override Type BindToType(string assemblyName, string typeName)

{

Type typeToDeserialize = null;

try

{

// For each assemblyName/typeName that you want to deserialize to

// a different type, set typeToDeserialize to the desired type.

String assemVer1 = Assembly.GetExecutingAssembly().FullName;

if (assemblyName.StartsWith("Config"))

{

assemblyName = assemVer1;

typeName = "Namespace.Assembly.Object" + typeName.Substring(typeName.LastIndexOf("."),(typeName.Length-typeName.LastIndexOf(".")));

}

typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));

}

catch (System.Exception ex1)

{

throw ex1;

}

finally

{

}

return typeToDeserialize;

}

}

Then use this class in the deserialization of the old file as follows:

Config config = new Config();

if ( !File.Exists( fileName ) )

{

return null;

}

BinaryFormatter formatter = new BinaryFormatter();

formatter.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;

Stream stream = new FileStream( fileName, FileMode.Open, FileAccess.Read, FileShare.Read );

try

{

// First Attempt to Deserialize the file with the new Assembly

config = (Config)formatter.Deserialize(stream);

}

catch (System.Runtime.Serialization.SerializationException ex)

// If the file is of the old type a SerializationException will occur

stream.Position =0;

BinaryFormatter OldFormatter = new BinaryFormatter();

OldFormatter.Binder = new VersionConfigToNamespaceAssemblyObjectBinder();

config = (Config) OldFormatter.Deserialize(stream);

}

catch (System.InvalidCastException)

// If the file is of the old type a InvalidCastException will occur

stream.Position =0;

BinaryFormatter OldFormatter = new BinaryFormatter();

OldFormatter.Binder = new VersionConfigToNamespaceAssemblyObjectBinder();

config = (Config) OldFormatter.Deserialize(stream);

}

catch (System.Exception ex)

{

throw ex;

}

finally

{

stream.Close();

}

return config;

}

This code effectively intervenes with the deserialization process and changes the actual types that the CLR will expect to deserialize then information into.  The above code will only work if you haven't changed the signature of the serialized assembly.  If you've made additional changes to the signature in the new version you would have to add more logig to the binder to handle the new types.

Hope this helps if you run into any serialization problems with versioning or changing assemblies.

Gavin

Posted on Friday, November 12, 2004 3:30 PM | Back to top


Comments on this post: Binary Serialization, Serialization Binder, Version and Type Escape..

# re: Binary Serialization, Serialization Binder, Version and Type Escape..
Requesting Gravatar...
What if I added a new field to my object, will I still be able to deserialize it back?
Left by Calvin on May 19, 2005 5:30 AM

# re: Binary Serialization, Serialization Binder, Version and Type Escape..
Requesting Gravatar...
This line...
typeName = "Namespace.Assembly.Object" ...
Do we take the words within the quotes literally? Or do we substitute?
Left by Dave on Oct 24, 2005 1:21 PM

# re: Binary Serialization, Serialization Binder, Version and Type Escape..
Requesting Gravatar...
I'm having a problem with Type.GetType -- it keeps returning 'Nothing' (I'm coding in VB .NET). I've checked and rechecked the typeName and assemblyName variables and all look good.
Left by Dave on Oct 24, 2005 3:00 PM

# re: Binary Serialization, Serialization Binder, Version and Type Escape..
Requesting Gravatar...
Dave,
Try System.Type.GetType instead of just Type.GetType.
Left by walt on Dec 07, 2005 8:25 PM

# re: Binary Serialization, Serialization Binder, Version and Type Escape..
Requesting Gravatar...
Type.GetType: type and assembly names are case sensitive (VB)
Left by Pet on Dec 14, 2005 5:59 PM

# re: Binary Serialization, Serialization Binder, Version and Type Escape..
Requesting Gravatar...
Would a binding redirect in the exe.config also work?
Left by Matthew on Dec 18, 2005 9:47 PM

# re: Binary Serialization, Serialization Binder, Version and Type Escape..
Requesting Gravatar...
We had the exact same problem and your solution worked like a charm! Thanks!

For those needing the VB.Net code, here it is:

NotInheritable Class Version21ToCurrentObjectBinder
Inherits SerializationBinder
Public Overrides Function BindToType(ByVal assemblyName As String, _
ByVal typeName As String) As Type

Dim typeToDeserialize As Type = Nothing

' We override the version with our current version
Dim assemVer1 As String = [Assembly].GetExecutingAssembly().FullName
assemblyName = assemVer1

Dim typeVer1 As String = "Namespace.Assembly.Object"

typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, _
assemblyName))

Return typeToDeserialize
End Function
End Class

When deserializing the stream, here's the code:

Dim bformatter As New BinaryFormatter
Dim bOldformatter As New BinaryFormatter
Dim stream As IO.FileStream

Try
' deserialize the whole she-bang
bformatter.AssemblyFormat = Formatters.FormatterAssemblyStyle.Simple
MyObject = CType(bformatter.Deserialize(stream), Object())
Catch ex As System.Runtime.Serialization.SerializationException
stream.Position = 0
bOldformatter.Binder = New Version21ToCurrentObjectBinder
MyObject= CType(bOldformatter.Deserialize(stream), Object())
Catch ex2 As System.Exception
Throw New System.Exception("Unable to deserialize: " & ex2.Message, ex2)
End Try
Left by Emmanuel Huna on May 09, 2006 5:14 PM

# re: Binary Serialization, Serialization Binder, Version and Type Escape..
Requesting Gravatar...
Awesome! I had the same problem too, and this worked great!

Just for information, I had to change the "Namespace.Assembly.Object" into my own namespace etc.
Left by Johnny on Feb 09, 2007 1:59 PM

# re: Binary Serialization, Serialization Binder, Version and Type Escape..
Requesting Gravatar...
I have coded in VB.NET
Will this work for memorystream also ?
and i am getting typeToDeserialize as nothing always.
And i haven't used namespace so what should the string typeName be ?
Left by aashwin on Mar 17, 2010 7:41 AM

Your comment:
 (will show your gravatar)


Copyright © Gavin Stevens | Powered by: GeeksWithBlogs.net