How to determine which class is executed if an interface property is deserialized inside the class?

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 to a file, this is pretty simple because the specific class behind IList is known and can be serialized.

But what happens if you try to deserialize this file back to memory?
It works without any direct error.

The problem arises if you try to add or remove something from the list. In this case, you will get an exception. And the root of this exception comes from the fact that the deserialized object is used as a concrete implementation for the IList array.

To avoid this problem in this simple example is easy. Just serialize the concrete store instead of the public property and make changes to 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 a more interesting question:

  • Why does Deserializer select an array type as a concrete implementation of the IList interface?
  • Is it possible to change the parameters that should be selected for each interface?
  • If I have my own interface and several implementations of this interface, is it possible to describe Deserializer, which concrete class should be used for this interface?
+4
source share
2 answers

If you use a NetDataContractSerializer that stores type information along with a serialized object, your problem should be resolved. However, this also reduces compatibility with non-.NET clients.

+1
source

You can solve this problem with DataContractSurrogate for deserialization, which 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, you just need to create an instance of the DataContractSerializer with this surrogate and use it for deserialization (it doesn't matter for serialization), for example:

 var serializer = new DataContractSerializer(type, new Type[]{}, Int32.MaxValue, false, true, new CustomDataContractSurrogate()); 

Or any of the other constructors that accept a surrogate.

Or (as a bonus to the answer), if you work with applications defined in the /web.config application, you can define a custom behavior that creates a data serializer with the surrogate above:

 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 exit , call the CreateService above to create a channel.

+3
source

Source: https://habr.com/ru/post/1309970/


All Articles