Data object update using SerializationBinder and Surrogate - c#

I am getting a Object of type 'Project1.Class1[]' cannot be converted to type 'Project2.Class1[]'.' when trying to take the data from class1 project 1 to project 2
The object getting passed is a List of Project1.Class1 that contains a subobject of Class2. So I created two Surrogate classes to handle converting the objects but I am getting that error before the surrogates handle updating the List of Class1.
[Serializable]
internal class Class1Upgrader
{
public BinaryFormatter CreateFormatter()
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
SurrogateSelector selector = new SurrogateSelector();
selector.AddSurrogate(typeof(Project1.Class1), new StreamingContext(StreamingContextStates.All), new Class1Surrogate());
selector.AddSurrogate(typeof(Project1.Class2), new StreamingContext(StreamingContextStates.All), new Class2Surrogate());
binaryFormatter.SurrogateSelector = selector;
binaryFormatter.Binder = new Class1Binder();
return binaryFormatter;
}
}
[Serializable]
internal class Class1Binder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
if (typeName == typeof(List<Project1.Class1>).FullName)
{
return typeof(List<Project2.Class1>);
}
return null;
}
}
Edit: After fixing the issue this solution will be a huge help for people that need to update varbinary fields in SQL within C#. Most of the code roots are in the answer to get people started. :)

[Serializable]
internal class Class1Upgrader
{
public BinaryFormatter CreateFormatter()
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
SurrogateSelector selector = new SurrogateSelector();
selector.AddSurrogate(typeof(Project2.Class1), new StreamingContext(StreamingContextStates.All), new Class1Surrogate());
selector.AddSurrogate(typeof(Project1.Class2), new StreamingContext(StreamingContextStates.All), new Class2Surrogate());
binaryFormatter.SurrogateSelector = selector;
binaryFormatter.Binder = new Class1Binder();
return binaryFormatter;
}
}
[Serializable]
internal class Class1Binder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
if (typeName.Contains("System.Collections.Generic.List`1[[Project1.Class1, Class1"))
{
return typeof(List<Project2.Class1>);
}
if (typeName == typeof(Project1.Class1).FullName)
{
return typeof(Project2.Class1);
}
return null;
}
}
internal class Class1Surrogate : ISerializationSurrogate
{
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
Project2.Class1 result = (Project2.Class1)obj;
//Handle the logic here to convert your old properties into your new one
return result;
}
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
throw new NotImplementedException();
}
}
internal class Class2Surrogate : ISerializationSurrogate
{
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
Project2.Class1 result = new Project2.Class1();
//Handle the logic here to convert your old properties into your new one
return result;
}
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
throw new NotImplementedException();
}
}
So the solution is to change your binder to return the new object and not the old to be passed through the surrogate. This is important when working with List of a object that you are trying to upgrade. So Adding typeof(Project2.Class1) the surrogate class will handle converting your properties. So in your SetObjectData in the surrogate in object will be typeof(Project2.Class1) like shown above in the Class1Surrogate.

Related

Customized Serialization

I have some objects that must be serialized:
class Displayable{
string name;
Sprite icon;
}
The icon field requires custom serialization since Sprites are already serialized (in different files, with their own format, by a game engine) and I only need to store a way to reference them (let's say a string, being the key inside a Dictionary<string, Sprite>).
Using BinaryFormatter I tried implementing ISerializable and ISerializationSurrogate but both of these methods create a new instance of the Sprite object on deserialization so they are not suitable for my case. I would like to have the same functionality of ISerializationSurrogate except I don't want the first parameter in SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) because I need to return an object I already have in memory instead of receiving a new instance from the deserializer and fill it with data.
I also tried external libraries like SharpSerializer but I don't like the constraints of having a public parameterless constructor and it doesn't really let you customize the serialization of special classes.
I've read about ProtoBuffers but that doesn't support inheritance and I need it somewhere else.
So my requirements are:
Inheritance support
Being able to define custom serialization for some types
Deserialization of those custom types shouldn't create instances on it's own
Is there any library doing this?
Otherwise, am I being too picky? How do you usually achieve serialization of references to objects stored somewhere else?
Thank you in advance.
Edit:
Here's what I'd like to have
public class SerializableSprite : ISerializationSurrogate
{
public static Dictionary<string, Sprite> sprites;
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) {
Sprite sprite = obj as Sprite;
info.AddValue("spriteKey", sprite.name);
}
// The first parameter in this function is a newly instantiated Sprite, which I don't need
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
return sprites[info.GetString("spriteKey")];
}
}
In order to prevent BinaryFormatter from creating a new instance of Sprite during deserialization, during serialization you can call SerializationInfo.SetType(Type) to specify alternate type information -- typically some proxy type -- to insert into the serialization stream. During deserialization SetObjectData() will be passed an instance of the proxy rather than the "real" type to initialize. This proxy type must in turn implement IObjectReference so that the "real" object can eventually be inserted into the object graph, specifically by looking it up in your table of sprites.
The following does this:
class ObjectReferenceProxy<T> : IObjectReference
{
public T RealObject { get; set; }
#region IObjectReference Members
object IObjectReference.GetRealObject(StreamingContext context)
{
return RealObject;
}
#endregion
}
public sealed class SpriteSurrogate : ObjectLookupSurrogate<string, Sprite>
{
static Dictionary<string, Sprite> sprites = new Dictionary<string, Sprite>();
static Dictionary<Sprite, string> spriteNames = new Dictionary<Sprite, string>();
public static void AddSprite(string name, Sprite sprite)
{
if (name == null || sprite == null)
throw new ArgumentNullException();
sprites.Add(name, sprite);
spriteNames.Add(sprite, name);
}
public static IEnumerable<Sprite> Sprites
{
get
{
return sprites.Values;
}
}
protected override string GetId(Sprite realObject)
{
if (realObject == null)
return null;
return spriteNames[realObject];
}
protected override Sprite GetRealObject(string id)
{
if (id == null)
return null;
return sprites[id];
}
}
public abstract class ObjectLookupSurrogate<TId, TRealObject> : ISerializationSurrogate where TRealObject : class
{
public void Register(SurrogateSelector selector)
{
foreach (var type in Types)
selector.AddSurrogate(type, new StreamingContext(StreamingContextStates.All), this);
}
IEnumerable<Type> Types
{
get
{
yield return typeof(TRealObject);
yield return typeof(ObjectReferenceProxy<TRealObject>);
}
}
protected abstract TId GetId(TRealObject realObject);
protected abstract TRealObject GetRealObject(TId id);
#region ISerializationSurrogate Members
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
var original = (TRealObject)obj;
var id = GetId(original);
info.AddValue("id", id);
// use Info.SetType() to force the serializer to construct an object of type ObjectReferenceWrapper<TRealObject> during deserialization.
info.SetType(typeof(ObjectReferenceProxy<TRealObject>));
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
// Having constructed an object of type ObjectReferenceWrapper<TRealObject>,
// look up the real sprite using the id in the serialization stream.
var wrapper = (ObjectReferenceProxy<TRealObject>)obj;
var id = (TId)info.GetValue("id", typeof(TId));
wrapper.RealObject = GetRealObject(id);
return wrapper;
}
#endregion
}
Then apply it to a BinaryFormatter as follows:
var selector = new SurrogateSelector();
var spriteSurrogate = new SpriteSurrogate();
spriteSurrogate.Register(selector);
BinaryFormatter binaryFormatter = new BinaryFormatter(selector, new StreamingContext());
Sample fiddle.
Update
While the code above works in .Net 3.5 and above, it apparently does not work in unity3d despite compiling successfully there because of some problem with IObjectReference. The following also works in .Net 3.5 and above and also avoids the use of IObjectReference by returning the real object from ISerializationSurrogate.SetObjectData(). Thus it should work in unity3d as well (confirmed in comments):
public sealed class SpriteSurrogate : ObjectLookupSurrogate<string, Sprite>
{
static Dictionary<string, Sprite> sprites = new Dictionary<string, Sprite>();
static Dictionary<Sprite, string> spriteNames = new Dictionary<Sprite, string>();
public static void AddSprite(string name, Sprite sprite)
{
if (name == null || sprite == null)
throw new ArgumentNullException();
sprites.Add(name, sprite);
spriteNames.Add(sprite, name);
}
public static IEnumerable<Sprite> Sprites
{
get
{
return sprites.Values;
}
}
protected override string GetId(Sprite realObject)
{
if (realObject == null)
return null;
return spriteNames[realObject];
}
protected override Sprite GetRealObject(string id)
{
if (id == null)
return null;
return sprites[id];
}
}
public abstract class ObjectLookupSurrogate<TId, TRealObject> : ISerializationSurrogate where TRealObject : class
{
class SurrogatePlaceholder
{
}
public void Register(SurrogateSelector selector)
{
foreach (var type in Types)
selector.AddSurrogate(type, new StreamingContext(StreamingContextStates.All), this);
}
IEnumerable<Type> Types
{
get
{
yield return typeof(TRealObject);
yield return typeof(SurrogatePlaceholder);
}
}
protected abstract TId GetId(TRealObject realObject);
protected abstract TRealObject GetRealObject(TId id);
#region ISerializationSurrogate Members
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
var original = (TRealObject)obj;
var id = GetId(original);
info.AddValue("id", id);
// use Info.SetType() to force the serializer to construct an object of type SurrogatePlaceholder during deserialization.
info.SetType(typeof(SurrogatePlaceholder));
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
// Having constructed an object of type SurrogatePlaceholder,
// look up the real sprite using the id in the serialization stream.
var id = (TId)info.GetValue("id", typeof(TId));
return GetRealObject(id);
}
#endregion
}
Sample fiddle #2.

How to handle deserialization of empty string into long in .NET?

The below illustration represents building Object of type TestClass2 using object of type TestClass1 with the help of Serialization/Deserialization.
TestClass1 and TestClass2 have the same structure except one of the members is string in TestClass1 but long in TestClass2.
public class TestClass1
{
public string strlong;
}
public class TestClass2
{
public long strlong;
}
TestClass1 objT1 = new TestClass1();
objT1.strlong = "20134567";
TestClass2 objT2;
JavaScriptSerializer serializer = new JavaScriptSerializer();
string JSON1 = serializer.Serialize(objT1);
objT2 = serializer.Deserialize<TestClass2>(JSON1);
After the operation, objT2 will have the values of objT1 but strlong will now be long as opposed to string.
The problem is, if the strlong value in objT1 is an empty string --> "", the deserialization fails with an exception "" is not a valid value for Int64.
If strlong is non empty string with just numeric characters, the current deserialization works. But I do not know the workaround when something like empty string appears.
For now, lets assume that
strlong will be in the range of long
Will just be a sequence of numeric characters i.e. it will not have . or , or / or any type of other characters
Have access to only objects for serialization and I cannot make modifications to TestClass1 or TestClass2.
If there is a simple way (or not) of Creating objects of one class using objects of another class, please mention that in the comments.
EDIT-Extending the logic
To extend the logic of solution given in the Answer below to Classes containing members of type other classes, I have used the serialization solution given below to the member items as well. In other words, if classes contain members of other classes, is there a better way of handling the deeper levels than the code below?
// **Item1 :**
// These are the subclasses and classes
// whose objects I am trying to serialize
// and deserialize from one type to another
public class SubClass1
{
public string toomuch;
public int number = 30;
}
public class SubClass2
{
public long toomuch;
public int number;
}
public class TestClass1
{
public string strlong;
public SubClass1 item2;
}
public class TestClass2
{
public long strlong;
public SubClass2 item2;
}
// **Item2 :**
// Solution from StackOverflow for serialization of
// empty string
public class TestClass1Converter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new Type[] { typeof(TestClass1) }; }
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
var data = obj as TestClass1;
var dic = new Dictionary<string, object>();
if (data == null)
{
return dic;
}
long val = 0;
long.TryParse(data.strlong, out val);
dic.Add("strlong", val);
// **Item3 :**
// trying to serialize and deserialize item2 which is of type SubClass1
// which might also have empty string
/*******************/
JavaScriptSerializer subClassSerializer = new JavaScriptSerializer();
subClassSerializer.RegisterConverters(new[] { new SubClass1Converter() });
string JSONstr = subClassSerializer.Serialize(data.item2);
dic.Add("item2", subClassSerializer.Deserialize<SubClass2>(JSONstr));
/*******************/
return dic;
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
}
// **Item4 :**
// Serialization for subclass
public class SubClass1Converter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new Type[] { typeof(SubClass1) }; }
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
var data = obj as SubClass1;
var dic = new Dictionary<string, object>();
if (data == null)
{
return dic;
}
long val = 0;
long.TryParse(data.toomuch, out val);
dic.Add("toomuch", val);
dic.Add("number", data.number);
return dic;
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
}
class Program
{
static void Main(string[] args)
{
TestClass1 objT1 = new TestClass1();
objT1.strlong = "";
SubClass1 objSub = new SubClass1();
objSub.toomuch = "";
objT1.item2 = objSub;
TestClass2 objT2;
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new TestClass1Converter() });
string JSON1 = serializer.Serialize(objT1);
objT2 = serializer.Deserialize<TestClass2>(JSON1);
}
}
You should declare your TestClass2.strlong as nullable.
public class TestClass2
{
public long? strlong;
}
Now you can have null in case when the TestClass1.strlong is empty string or null.
Here is UPDATE in case that you haven't access to modify the classes.
You should add to the serializer the converter via RegisterConverters to customize conversion. Here is the example:
public class TestClass1Converter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new Type[] { typeof(TestClass1)}; }
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
var data = obj as TestClass1;
var dic = new Dictionary<string, object>();
if(data == null)
{
return dic;
}
long val = 0;
long.TryParse(data.strlong, out val);
dic.Add("strlong", val);
return dic;
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
}
This converter will serialize strlong to 0 in case when it is not convertible to long. You can use it in this way:
TestClass1 objT1 = new TestClass1();
objT1.strlong = "444";
TestClass2 objT2;
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new [] {new TestClass1Converter()});
string JSON1 = serializer.Serialize(objT1);
objT2 = serializer.Deserialize<TestClass2>(JSON1);

How to serialize list property of a dynamic object

I have an issue with serialization for a wcf service (JSON output).
I use dynamicobject to return ligth JSON for my REST service.
This code return an empty result (impossible to serialize):
public DynamicJsonObject DoWork()
{
dynamic result = new DynamicJsonObject();
result.values = new List<int>() { 1, 2 };
}
but this code works perfectly
public DynamicJsonObject DoWork()
{
dynamic result = new DynamicJsonObject();
result.values = 1;
}
My DynamicJsonObject class is :
[Serializable]
public class DynamicJsonObject : DynamicObject, ISerializable
{
private IDictionary<String, Object> Dictionary { get; set; }
public DynamicJsonObject()
{
Dictionary = new Dictionary<String, Object>();
}
public DynamicJsonObject(SerializationInfo info, StreamingContext context)
{
Dictionary = new Dictionary<String, Object>();
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var hasKey = Dictionary.ContainsKey(binder.Name);
result = hasKey ? Dictionary[binder.Name] : null;
return hasKey;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
Dictionary[binder.Name] = value;
return true;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
foreach (String key in Dictionary.Keys)
{
info.AddValue(key.ToString(), Dictionary[key]);
}
}
}
So I got this error Error 324 (net::ERR_EMPTY_RESPONSE) instead of this JSON result {values: [1,2]}
I found the solution. You should to declare manualy list of serializable.
In my exemple, I can add the attribute KnownType on result object
[Serializable]
[KnownType(typeof(List<int>))]
public class DynamicJsonObject : DynamicObject, ISerializable
{
...
}
the other solution is to use the ServiceKnownType on the wcf service class
[ServiceContract]
[ServiceKnownType(typeof(List<int>))]
public interface IDataService
{
...
}
For information, you can use generic attribute like KnownType(typeof(List)
List<UserDetails> list = new List<UserDetails>();//UserDetails is my class name it has some properties
for serialization you can use this code:
var objSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
string sJSON = objSerializer.Serialize(list);
return list;

Generic BinaryReader

I'm writing binary serialiser/deserialser to convert a number of object types to/from a byte stream. The objects represent API commands and their associated responses for a device connected by Bluetooth or USB. I'm using the BinaryWriter & BinaryReader to write/read to/from the stream.
The serialiser is easy. The properites to be serialised are tagged with an attribute that specifies the order in which they are to be written to the byte stream. I iterate through the properties using reflection and overload resolution handles picking the correct Write(...) method of the BinaryWriter.
The deserialiser is not quite so simple. Again I can iterate through the properites in the particular response class that I'm expecting to determine the types that need to be read from the stream. The tricky bit is picking the correct method to call on the BinaryReader to read the value I need. I've thought of two approaches.
A big switch statement that calls the correct ReadXXXX() method based on the type to be read.
Use the name of the type I need to build the name of the method I need in a string, and then invoke the method using relection.
Is there a simpler way I'm not thinking of? It's just a shame you can't do overload resolution based on the return type you want.
I've used option 1 (big switch statement) in a binary deserializer. A cleaner method could be something like:
{
object result;
BinaryReader ...;
foreach (var propertyInfo in ...)
{
Func<BinaryReader, object> deserializer;
if (!supportedTypes.TryGetValue(propertyInfo.PropertyType, out deserializer))
{
throw new NotSupportedException(string.Format(
"Type of property '{0}' isn't supported ({1}).", propertyInfo.Name, propertyInfo.PropertyType));
}
var deserialized = deserializer(reader);
propertyInfo.SetValue(result, deserialized, null);
}
}
private static Dictionary<Type, Func<BinaryReader, object>> supportedTypes = new Dictionary<Type, Func<BinaryReader, object>>
{
{ typeof(int), br => br.ReadInt32() },
// etc
};
Another option is to let the command classes themselves do the serialization:
interface IBinarySerializable
{
void Serialize(BinaryWriter toStream);
void Deserialize(BinaryReader fromStream);
}
Then in your commands:
abstract class Command : IBinarySerializable
{
}
class SomeCommand : Command
{
public int Arg1 { get; set; }
public void Serialize(BinaryWriter toStream)
{
toStream.Write(Arg1);
}
public void Deserialize(BinaryReader fromStream)
{
Arg1 = fromStream.ReadInt32();
}
}
And generic serialization methods:
void Serialize<T>(T obj) where T : IBinarySerializable
{
obj.Serialize(_stream);
}
T Deserialize<T>() where T : new(), IBinarySerializable
{
var result = new T();
result.Deserialize(_stream);
return result;
}
But this way you might end up duplicating some code. (On the other hand, derived classes can call their parent class versions of Serialize/Deserialize if that makes sense in your scenario, and that works nicely.)
I do not know if I fully understand what you are trying to do. But it very much sound like you shall take a closer look at the BinaryFormatter and its serialization surrogates in .NET. The BinaryFormatter let you easily Serialize and Deserialize objects and the serialization surrogates let you add your custom serialization and deserialization logic and also make it possible to remapp one object to antoher.
Take a look here:
BinaryFormatter
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter.aspx
ISerializationSurrogate
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializationsurrogate.aspx
SurrogateSelector
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.surrogateselector.aspx
SerializationBinder
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.serializationbinder.aspx
You can also see a small example here, where I have a method that can serialize any object to a base64 encoded string, and then a deserialize-method that can deserialize this string. I also add a SerializationBinder to the formatter that remapp the serialization of the type MyOldClass to the type MyNewClass and also add a custom ISerializationSurrogate that can process the values of the fields in the object before it is added to the ner class.
public class SerializeDeserializeExample {
public string Serialize(object objectToSerialize) {
using(var stream = new MemoryStream()) {
new BinaryFormatter().Serialize(stream, objectToSerialize);
return Convert.ToBase64String(stream.ToArray());
}
}
public object Deserialize(string base64String) {
using(var stream = new MemoryStream(Convert.FromBase64String(base64String))) {
var formatter = new BinaryFormatter();
var surrogateSelector = new SurrogateSelector();
formatter.SurrogateSelector = surrogateSelector;
formatter.Binder = new DeserializationBinder(surrogateSelector);
return formatter.Deserialize(stream);
}
}
}
public class MyDeserializationBinder : SerializationBinder {
private readonly SurrogateSelector surrogateSelector;
public MyDeserializationBinder(SurrogateSelector surrogateSelector) {
this.surrogateSelector = surrogateSelector;
}
public override Type BindToType(string assemblyName, string typeName) {
if(typeName.Equals("MyOldClass", StringComparison.InvariantCultureIgnoreCase)) {
return RemapToType();
}
return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
}
private Type RemapToType() {
var remapToType = typeof(MyNewClass);
surrogateSelector.AddSurrogate(remapToType,
new StreamingContext(StreamingContextStates.All),
new MyCustomDeserializationSurrogate());
return remapToType;
}
}
public sealed class MyCustomDeserializationSurrogate : ISerializationSurrogate {
public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context) {
throw new NotImplementedException();
}
public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
var objectType = obj.GetType();
var fields = GetFields(objectType);
foreach(var fieldInfo in fields) {
var fieldValue = info.GetValue(fieldInfo.Name, fieldInfo.FieldType);
fieldValue = DoSomeProcessing(fieldValue);
fieldInfo.SetValue(obj, fieldValue);
}
return obj;
}
private static IEnumerable<FieldInfo> GetFields(Type objectType) {
return objectType.GetFields(BindingFlags.Instance | BindingFlags.DeclaredOnly |
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
}
private static object DoSomeProcessing(object value){
//Do some processing with the object
}
}

How to define which class is taken if an interface property is deserialized within a class?

Just imagine you have the following class
[DataContract]
public class NamedList
{
[DataMember]
public string Name { get; set; }
[DataMember]
public IList<string> Items { get; private set; }
public DumpList(string name)
{
Name = name;
Items = new List<string>();
}
}
If you serialize this into a file, it is quite easy, cause the concrete class behind the IList is known and can be serialized.
But what happens if you try to deserialize this file back into memory?
It works without any direct error occuring.
The problem comes if you try to add or remove something from the list. In that case you'll get an exception. And the root of this exception comes from the case that the deserialized object uses as concrete implementation for the IList an Array.
To avoid this problem in this simple example is easy. Just serialize the concrete backing store instead of the public property and make the change in the constructor:
[DataMember(Name = "Items")]
private List<string> _Items;
public IList<string> Items
{
get
{
return _Items;
}
}
public DumpList(string name)
{
Name = name;
_Items = new List<string>();
}
But the more interesting question is:
Why chooses the Deserializer the Array type as concrete implementation of the IList interface?
Is it possible to change the settings which class should be taken for each interface?
If i have a self defined interface and several implementations of this interface, is it possible to tell the Deserializer which concrete class should be taken for a given interface?
You can solve this using a DataContractSurrogate for the deserialization, that replaces IList with List.
public class CustomDataContractSurrogate : IDataContractSurrogate
{
// The only function you should care about here. The rest don't do anything, just default behavior.
public Type GetDataContractType(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(ICollection<>)))
{
return (typeof(List<>).MakeGenericType(type.GetGenericArguments().Single()));
}
return type;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
return obj;
}
public object GetDeserializedObject(object obj, Type targetType)
{
return obj;
}
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
return null;
}
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
return null;
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
return null;
}
public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
{
return typeDeclaration;
}
}
Basically that's it, you just need to create your DataContractSerializer instance with that surrogate and use it for deserialization (for serialization it won't matter), for example:
var serializer = new DataContractSerializer(type, new Type[]{}, Int32.MaxValue, false, true, new CustomDataContractSurrogate());
Or any of the other constructors that take a surrogate.
Or, (as a bonus to the answer) if you're working with app/web.config-defined services, you can define a custom behavior that creates a data contract serializer with the above surrogate:
public class CustomDataContractSerializerBehavior : DataContractSerializerOperationBehavior
{
public CustomDataContractSerializerBehavior(OperationDescription operation)
: base(operation)
{
}
public CustomDataContractSerializerBehavior(OperationDescription operation, DataContractFormatAttribute dataContractFormatAttribute)
: base(operation, dataContractFormatAttribute)
{
}
public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns,
IList<Type> knownTypes)
{
return new DataContractSerializer(type, knownTypes, Int32.MaxValue, false, true, new CustomDataContractSurrogate());
}
public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name,
XmlDictionaryString ns, IList<Type> knownTypes)
{
return new DataContractSerializer(type, knownTypes, Int32.MaxValue, false, true, new CustomDataContractSurrogate());
}
}
Finally you can use this behavior:
public static IMyDataServiceContract CreateService()
{
var factory = new ChannelFactory<IMyDataServiceContract>("MyServiceName");
SetDataContractSerializerBehavior(factory.Endpoint.Contract);
return factory.CreateChannel();
}
private static void SetDataContractSerializerBehavior(ContractDescription contractDescription)
{
foreach (OperationDescription operation in contractDescription.Operations)
{
ReplaceDataContractSerializerOperationBehavior(operation);
}
}
private static void ReplaceDataContractSerializerOperationBehavior(OperationDescription description)
{
DataContractSerializerOperationBehavior dcsOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (dcsOperationBehavior != null)
{
description.Behaviors.Remove(dcsOperationBehavior);
description.Behaviors.Add(new CustomDataContractSerializerBehavior(description));
}
}
To finish the job, call the above CreateService somewhere to create the channel.
If you use the NetDataContractSerializer, which stores type information along with the serialized object, your problem should be solved. However, it does at the same time reduce interoperability to non-.NET clients.

Categories