Tuesday, March 11, 2008

Using the SerializationBinder for versioning

Normally when building an application I am a strong advocate for storing long term persistent data in a relational database such as SQL Server; however, with all things there always exceptions.

I am currently working on an application, that has at its core a complex object graph, with several levels of inheritance, and all sorts of references between the internal objects. At some point in the future I will be storing this object in normalized tables inside SQL server; however, because this project started out as a prototype which was loosely defined, and due to the wide variety of references within this object (And the undefined requirements of what it contains and how it works). I opted to store the object as a blob in the database.

This has been a huge win from a productivity standpoint since we can add all sorts of things to the object without worrying about the persistence tier. The negative of this comes when we want to change something. The .net framework has several hooks in place that help you modify an object which you using binary serialization with. One of these hooks is the SerializationBinder.

The SerializationBinder provides a means for you to do type replacement. Say for example you are building version 1 of an application. You spend hours designing testing and implementing it, and you ship the application, and your customers love the application and start asking for version 2. Unless you are superman you will probably not have designed everything ideally to support the features in version 2 (And if you did drop me a line, I have a job for you!).

I found myself in a similar situation, where I defined an object called MyCompany.Domain.Note. During our version 2 implementation, I realized that this shouldn't be a MyCompany.Domain.Note but instead needs to be a MyCompany.Domain.Widget.Note. So I change the class around, and run the application. This will result in all sorts of SerilizationExceptions since the Serializer is looking for MyCompany.Domain.Note which no longer exists.

This where the SerializationBindercomes into play. The SerializationBinder is an abstract class so you need to define a new class.


using System.Runtime.Serialization;
public class WidgetSerialzationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
}
}


Implementing this class is very simple. In our case we want to make sure anytime we see MyCompany.Domain.Note we replace it with MyCompany.Domain.Widget.Note. Place this code in the BindToType method.


if (typeName.Contains("MyCompany.Domain.Note"))
{
typeName = typeName.Replace("MyCompany.Domain.Note", "MyCompany.Domain.Widget.Note");
}
return Type.GetType(String.Format("{0}, {1}",typeName, assemblyName));


The reason we are using contains/replace on the typeName is that we might know for sure what the typeName should be. For example if you have a List<mycompany.domain.note> you will need to change it to List<mycompany.domain.widget.note>. The actual type name in this case is: System.Collections.Generic.List`1[[MyCompany.Domain.Widget.Note, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]

To use the binder you will need to set the Binder property on your BinaryFormatter. For example:


 public static object Deserialize(byte[] data,SerializationBinder binder)
{
object obj;


MemoryStream streamMemory = new MemoryStream(data);
BinaryFormatter formatter = new BinaryFormatter();

formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
formatter.Binder = binder;
obj = formatter.Deserialize(streamMemory);
return obj;
}


Enjoy!

No comments: