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