Customized Serialization - c#

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.

Related

Data object update using SerializationBinder and Surrogate

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.

Serialize complex type as simple type JSON

I have a complex type that is to be serialized as a string. I have this working as desired using IXmlSerializable and am now trying to get it to work for JSON messages as well. Currently I get it to serialize via the DataContractSerializer to the following:
<MyXmlRootElement>_value's contents</MyXmlRootElement>
For the JSON output I would like to just receive a JSON string with the _value's contents.
This type implements more than described below to also affect the generated wsdl through a few other attributes that hook into our web services framework. So the generated wsdl/xsd all looks great, it just isn't giving me the desired JSON.
[XmlRoot(ElementName = "MyXmlRootElement", Namespace = "MyNamespace"]
public class ComplexType : IXmlSerializable
{
private string _value;
public ComplexType(string value) { _value = value; }
#region IXmlSerialiable Implementation
public XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader)
{
_value = reader.ReadString();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteString(_value);
}
#endregion
}
I tried implementing ISerializable and this does affect the generated JSON, but it still places it in a complex (object) type, i.e, { "ValueKey": "_value's contents" }. Any idea how I can get it to serialize to a pure string, no curly braces?
The solution was easier than expected. You can register a JsonConverter with GlobalConfiguration. I made an abstract wrapper around JsonConverter as below, the idea for which came from the following SO thread: How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?.
public abstract class CustomJsonConverter<T, TResult> : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load token from stream
var token = JToken.Load(reader);
// Create target object based on token
var target = Create(objectType, token);
var targetType = target.GetType();
if (targetType.IsClass && targetType != typeof(string))
{
// Populate the object properties
var tokenReader = token.CreateReader();
CopySerializerSettings(serializer, tokenReader);
serializer.Populate(token.CreateReader(), target);
}
return target;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
T typedValued = (T)value;
TResult valueToSerialize = Convert(typedValued);
serializer.Serialize(writer, valueToSerialize);
}
public override bool CanConvert(Type objectType)
{
return typeof (T) == objectType;
}
protected virtual T Create(Type type, JObject jObject)
{
// reads the token as an object type
if (typeof(TResult).IsClass && typeof(T) != typeof(string))
{
return Convert(token.ToObject<TResult>());
}
var simpleValue = jObject.Value<TResult>();
return Convert(simpleValue);
}
protected abstract TResult Convert(T value);
protected abstract T Convert(TResult value);
private static void CopySerializerSettings(JsonSerializer serializer, JsonReader reader)
{
reader.Culture = serializer.Culture;
reader.DateFormatString = serializer.DateFormatString;
reader.DateTimeZoneHandling = serializer.DateTimeZoneHandling;
reader.DateParseHandling = serializer.DateParseHandling;
reader.FloatParseHandling = serializer.FloatParseHandling;
}
}
You can then use this to do something like the following
public class DateTimeToStringJsonConverter : CustomJsonConverter<DateTime, string>
{
protected override string Convert(DateTime value)
{
return value.ToString();
}
protected override DateTime Convert(string value)
{
return DateTime.Parse(value);
}
}
And then finally register an instance with GlobalConfiguration in Global.asax.cs
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new DateTimeToStringJsonConverter());
Any thoughts/ideas/opinions? Thanks!
EDIT: 2018.10.16 - Updated code to handle value types as the source type and to copy serializer settings over thanks to the following comment from the original SO answer:
NOTE: This solution is all over the internet, but has a flaw that manifests itself in rare occasions. The new JsonReader created in the ReadJson method does not inherit any of the original reader's configuration values (Culture, DateParseHandling, DateTimeZoneHandling, FloatParseHandling, etc...). These values should be copied over before using the new JsonReader in serializer.Populate().
import this namespace System.Web.Script.Serialization;
string SerializeObject()
{
var objs = new List<Test>()
var objSerialized = new JavaScriptSerializer();
return objSerialized .Serialize(objs);
}
I use as example a List but you will use your object instead.

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