I'm trying to make the BinaryFormatter
work across different versions of my assembly. The actual class I want to deserialize to is exactly the same in each assembly version, but on deserialization, because the objects are serialized include the assembly name they came from, the BinaryFormatter
is complaining that it can't find the right assembly. So I created a custom SerializationBinder
that tells the BinaryFormatter
to always deserialize to the current assembly version.
My scheme works and can deserialize objects correctly, but it doesn't work if my object is a List of T, where T was a type serialized from an older version of my assembly.
Is there some way to make this work with Lists and other generic types where the type parameter is a class from my assembly?
//the object i want to deserialize
class MyObject
{
public string Name{get;set;}
}
//my binder class
class MyBinder : SerializationBinder
{
static string assemblyToUse = typeof (MyObject).Assembly.FullName;
public override Type BindToType(string assemblyName, string typeName)
{
var isMyAssembly = assemblyName.StartsWith("oldAssemblyName");
var assemblyNameToUse = isMyAssembly ? assemblyToUse : assemblyName;
var tn = typeName + ", " + assemblyNameToUse;
return Type.GetType(tn);
}
}
//my deserialize method
static object BinaryDeserialize(string input)
{
var arr = Convert.FromBase64String(input);
var ms = new MemoryStream(arr);
ms.Seek(0, SeekOrigin.Begin);
var bf = new BinaryFormatter();
bf.Binder = new MyBinder();
var obj = bf.Deserialize(ms);
return obj;
}
static void Test()
{
//this works
//serialized(new MyObject());
var str = ReadSerialized_MyObject();
var obj = BinaryDeserialize(str);
//this doesn't work
//serialized(new List<MyObject>());
var str2 = ReadSerialized_List_of_MyObject();
var obj = BinaryDeserialize(str2);
}
If you serialized an instance of List< MyClass > from your version 1.0.0.0 assembly, the SerializationBinder.BindToType function will be asked to provide this type:
System.Collections.Generic.List`1[[MyAssembly.MyClass, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=12345678901234567890]]
In order to remap the List< MyClass > type to your version 2.0.0.0 assembly, you need to change the type name to this:
System.Collections.Generic.List`1[[MyAssembly.MyClass, MyAssembly]]
The main point to note, is that the assembly name is not fully qualified. If you try to fully qualify the assembly name with a 2.0.0.0 version number, it will not work.
Application A creates a Serialized Binary formatter file "SerializedList.bin" which contains List(Result) where Result is the Serializable object. Now Application B wants to DeSerialize the file and load into List(Result) object. This is how I got it working..
Application A Assembly Name is "Serialize"
Application B Assembly Name is "DeSerialize"
Application A Code(Serialize):
namespace Serialize
{
class Program
{
static void Main(string[] args)
{
List<Result> result = ;//Get From DB
IFormatter formatter = new BinaryFormatter();
Stream sStream = new FileStream(
"SerializedList.bin",
FileMode.CreateNew,
FileAccess.Write,
FileShare.None);
formatter.Serialize(sStream, result);
sStream.Close();
}
}
}
Some Result Object:
[Serializable]
public class Result
{
public decimal CONTACT_ID { get; set; }
public decimal INSTITUTION_NBR { get; set; }
}
Application B Code (DeSerialize):
namespace DeSerialize
{
class Program
{
static void Main(string[] args)
{
IFormatter formatter = new BinaryFormatter();
string fromTypeName = "System.Collections.Generic.List`1[[Serialize.Result, Serialize, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]";
string fromTypeName1 = "Serialize.Result";
string toTypename = "System.Collections.Generic.List`1[DeSerialize.Result]";
string toTypename1 = "DeSerialize.Result";
string toTypeAssemblyName = Assembly.GetExecutingAssembly().FullName;
DictionarySerializationBinder dic = new DictionarySerializationBinder();
dic.AddBinding(fromTypeName, toTypename);
dic.AddAssemblyQualifiedTypeBinding(fromTypeName1, toTypename1, toTypeAssemblyName);
formatter.Binder = dic;
Stream dStream = new FileStream(
"SerializeList.bin",
FileMode.Open,
FileAccess.Read,
FileShare.Read);
List<Result> listDS =
(List<Result>)formatter.Deserialize(dStream);
dStream.Close();
}
}
sealed class DictionarySerializationBinder : SerializationBinder
{
Dictionary<string, Type> _typeDictionary = new Dictionary<string, Type>();
public override Type BindToType(string assemblyName, string typeName)
{
Type typeToReturn;
if (_typeDictionary.TryGetValue(typeName, out typeToReturn))
{
return typeToReturn;
}
else
{
return null;
}
}
public void AddBinding(string fromTypeName, string toTypeName)
{
Type toType = Type.GetType(toTypeName);
if (toType == null)
{
throw new ArgumentException(string.Format(
"Help, I could not convert '{0}' to a valid type.", toTypeName));
}
_typeDictionary.Add(fromTypeName, toType);
}
public void AddAssemblyQualifiedTypeBinding(string fromTypeName, string toTypeName, string toTypeAssemblyName)
{
Type typeToSerializeTo = GetAssemblyQualifiedType(toTypeAssemblyName, toTypeName);
if (typeToSerializeTo == null)
{
throw new ArgumentException(string.Format(
"Help, I could not convert '{0}' to a valid type.", toTypeName));
}
_typeDictionary.Add(fromTypeName, typeToSerializeTo);
}
private static Type GetAssemblyQualifiedType(string assemblyName, string typeName)
{
return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
}
}
}
Just handle AppDomain.AssemblyResolve
event and return the desired assembly when calling Type.GetType
method. That's as simple as that!
来源:https://stackoverflow.com/questions/5794686/serializationbinder-with-listt