Wednesday, February 24, 2010

C# – BinaryFormatter.Deserialize is “Unable to find assembly”

While testing a SQL CLR procedure recently I came across an issue with the binary formatter deserialization failing because it could not find the assembly for the type (even though the type is within the same assembly as the deserialization code).

The database application I have been working on has an initial request object (from a web service or web site) that is serialized using the binary formatter and stored in a Request table. It is then added into a Queue to be processed by one or more Service Broker services at a later time.

The serialization and deserialization of the request object is performed by a SQL CLR procedure, utilising the “System.Runtime.Serialization.Formatters.Binary.BinaryFormatter” formatter.

While testing that the Service Broker was configured correctly I was reusing the same Request table data, where the request object had previously been serialized and saved to the table. However I quickly noticed that after re-deploying the SQL CLR project I was running into an error while deserializing the object (note: the type being serialized/deserialized is in the same assembly as the code that serializes it):

Unable to find assembly 'MyAssembly; Version=1.0.3463.18923; Culture=neutral; PublicKeyToken=40e3171cc8066fe6'.

After a little googling it became apparent that the binary formatter stores the type information within the serialized object so that it knows what type to deserialize it to (that makes sense), however this becomes a big issue if you are storing data for periods of time and need to maintain backwards compatibility with new versions of the assembly containing the type information.

Luckily the guys at MS had thought of this and provided us with the “System.Runtime.Serialization.SerializationBinder” class. By inheriting from this class it is possible to redirect all the requests for types from the binary formatter to the types of your choice.

Here is a sample that will allow the types to be found in the current assembly regardless of which version of the assembly originally created the serialized stream:

01sealed class AllowAllAssemblyVersionsDeserializationBinder : System.Runtime.Serialization.SerializationBinder
02{
03    public override Type BindToType(string assemblyName, stringtypeName)
04    {
05        Type typeToDeserialize = null;
06 
07        String currentAssembly = Assembly.GetExecutingAssembly().FullName;
08 
09        // In this case we are always using the current assembly
10        assemblyName = currentAssembly;
11 
12        // Get the type using the typeName and assemblyName
13        typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
14            typeName, assemblyName));
15 
16        return typeToDeserialize;
17    }
18}
19 
20public static MyRequestObject Deserialize(byte[] b)
21{
22    MyRequestObject mro = null;
23    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = newSystem.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
24    System.IO.MemoryStream ms = new System.IO.MemoryStream(b);
25 
26    // To prevent errors serializing between version number differences (e.g. Version 1 serializes, and Version 2 deserializes)
27    formatter.Binder = new AllowAllVersionsDeserializationBinder();
28 
29    // Allow the exceptions to bubble up
30    // System.ArgumentNullException
31    // System.Runtime.Serialization.SerializationException
32    // System.Security.SecurityException
33    mro = (MyRequestObject)formatter.Deserialize(ms);
34    ms.Close();
35    return mro;
36}

In my case the type itself had not changed between assembly versions, only the assembly version information. However the MSDN link below shows an example of allowing backwards compatibility between different versions of the type where the type itself may have changed significantly.

(http://msdn.microsoft.com/en-us/library/system.runtime.serialization.serializationbinder(VS.71).aspx)

P.S. this would also be a great solution if you are attempting to deserialize an object that was created in a different assembly that you cannot reference in your own project for some reason – providing you know the structure of the object and create a similar class in your own assembly.

3 comments:

  1. actually this reason can be avoided by generating a dll library for the common DTO object from another VS project and adding the built dll file as a reference to both serialization and deserialization classes!

    ReplyDelete
  2. keep it up, obviously blogging has been playing key role in sharing our experience with others.
    Hope this will provide more technological innovations for sure!!

    ReplyDelete
  3. this is for custom class which have list and non list object


    sealed class VersionDeserializationBinder : SerializationBinder
    {
    public override Type BindToType(string assemblyName, string typeName)
    {
    Type typeToDeserialize = null;
    string currentAssemblyInfo = Assembly.GetExecutingAssembly().FullName;

    //my modification
    string currentAssemblyName = currentAssemblyInfo.Split(',')[0];
    if (assemblyName.StartsWith(currentAssemblyName))assemblyName = currentAssemblyInfo;

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

    ReplyDelete