Problem statement
.Net objects are referenced in cyclic (mutual) nature.
DataContractSerializer de-serialization uses to fail in case of forward compatibility (i.e. when it tried to read from ExtensionDataObject).
Discussion
using System.Runtime.Serialization;
Version2 Project
[DataContract(Name = "People", Namespace = "Tests.DataContract")]
[KnownType(typeof(Person))]
[KnownType(typeof(AnotherPerson))]
public class People : IExtensibleDataObject
{
[DataMember]
public object Person { get; set; }
[DataMember]
public object AnotherPerson { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
[DataContract(Name = "Person", Namespace = "Tests.DataContract")]
public class Person : IExtensibleDataObject
{
[DataMember]
public string Name { get; set; }
/* This is added in this version */
[DataMember]
public AnotherPerson AnotherPerson { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
[DataContract(Name = "AnotherPerson", Namespace = "Tests.DataContract")]
public class AnotherPerson : IExtensibleDataObject
{
[DataMember]
public string Name { get; set; }
/* This is added in this version */
[DataMember]
public Person FriendPerson { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
ConsoleApplication
/* ignoreExtensionDataObject is false
* preserveObjectReferences is true
*/
DataContractSerializer serializer = new DataContractSerializer(typeof(People), null, int.MaxValue, false, true, null, null);
var people = new People();
var person = new Person() { Name = "Person" };
var anotherPerson = new AnotherPerson() { Name = "AnotherPerson"};
people.Person = person;
people.AnotherPerson = anotherPerson;
/* Cyclic references
* person object gets a reference of anotherPerson object
* FriendPerson property - anotherPerson object gets a reference of person object
*/
person.AnotherPerson = anotherPerson;
anotherPerson.FriendPerson = person;
using (var writer = new XmlTextWriter("../../../../SavedFiles/Version2Saved.xml", null) { Formatting = Formatting.Indented })
{
serializer.WriteObject(writer, people);
writer.Flush();
}
Serializer.WriteObject() -> successful – means XML is generated.
Now, we will turn back to Version1 application where it tries to de-serialize the same XML. In Version1, it will have less number of data members in comparison to the Version2 data contracts.
Version1 project
[DataContract(Name = "People", Namespace = "Tests.DataContract")]
[KnownType(typeof(Person))]
[KnownType(typeof(AnotherPerson))]
public class People : IExtensibleDataObject
{
[DataMember]
public object Person { get; set; }
[DataMember]
public object AnotherPerson { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
[DataContract(Name = "Person", Namespace = "Tests.DataContract")]
public class Person : IExtensibleDataObject
{
[DataMember]
public string Name { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
[DataContract(Name = "AnotherPerson", Namespace = "Tests.DataContract")]
public class AnotherPerson : IExtensibleDataObject
{
[DataMember]
public string Name { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
ConsoleApplication
DataContractSerializer serializer = new DataContractSerializer(typeof(People), null, int.MaxValue, false, true, null, null);
People loadedPeople = null;
using (var reader = new XmlTextReader("../../../../SavedFiles/Version2Saved.xml"))
{
loadedPeople = (People)serializer.ReadObject(reader);
}
Observation
0:001> kL
# ChildEBP RetAddr
00 002aef00 74c48fdb kernelbase!RaiseException
01 002aefa0 74c49c19 clr!RaiseTheExceptionInternalOnly+0x27f
02 002af074 7091c2e4 clr!IL_Throw+0x13e
03 002af16c 707becf1 system_runtime_serialization_ni!System.Runtime.Serialization.ObjectDataContract.ReadXmlValue(System.Runtime.Serialization.XmlReaderDelegator, System.Runtime.Serialization.XmlObjectSerializerReadContext)+0xe0
04 002af178 707beb5e system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContext.ReadDataContractValue(System.Runtime.Serialization.DataContract, System.Runtime.Serialization.XmlReaderDelegator)+0x11
05 002af1b0 708f673a system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(System.Runtime.Serialization.XmlReaderDelegator, System.String, System.String, System.Type, System.Runtime.Serialization.DataContract ByRef)+0x7e
06 002af1dc 708eacf7 system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(System.Runtime.Serialization.XmlReaderDelegator, System.Type, System.String, System.String)+0x5e
07 002af200 708ea162 system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContext.DeserializeFromExtensionData(System.Runtime.Serialization.IDataNode, System.Type, System.String, System.String)+0xcb
08 002af220 709946b1 system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContext.GetExistingObject(System.String, System.Type, System.String, System.String)+0x72
09 002af240 707beb15 system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContext.TryHandleNullOrRef(System.Runtime.Serialization.XmlReaderDelegator, System.Type, System.String, System.String, System.Object ByRef)+0x1d5ac1
0a 002af284 707c8ad7 system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(System.Runtime.Serialization.XmlReaderDelegator, System.String, System.String, System.Type, System.Runtime.Serialization.DataContract ByRef)+0x35
0b 002af2ac 07bb0199 system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(System.Runtime.Serialization.XmlReaderDelegator, Int32, System.RuntimeTypeHandle, System.String, System.String)+0x57
0c 002af2e8 707c238e system_runtime_serialization_ni!DynamicClass.ReadPeopleFromXml(System.Runtime.Serialization.XmlReaderDelegator, System.Runtime.Serialization.XmlObjectSerializerReadContext, System.Xml.XmlDictionaryString[], System.Xml.XmlDictionaryString[])+0x131
0d 002af300 707becf1 system_runtime_serialization_ni!System.Runtime.Serialization.ClassDataContract.ReadXmlValue(System.Runtime.Serialization.XmlReaderDelegator, System.Runtime.Serialization.XmlObjectSerializerReadContext)+0x2e
0e 002af30c 707bebd8 system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContext.ReadDataContractValue(System.Runtime.Serialization.DataContract, System.Runtime.Serialization.XmlReaderDelegator)+0x11
0f 002af344 707b4526 system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(System.Runtime.Serialization.XmlReaderDelegator, System.String, System.String, System.Type, System.Runtime.Serialization.DataContract ByRef)+0xf8
10 002af370 707bbc6c system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(System.Runtime.Serialization.XmlReaderDelegator, System.Type, System.Runtime.Serialization.DataContract, System.String, System.String)+0x6a
11 002af39c 707bc367 system_runtime_serialization_ni!System.Runtime.Serialization.DataContractSerializer.InternalReadObject(System.Runtime.Serialization.XmlReaderDelegator, Boolean, System.Runtime.Serialization.DataContractResolver)+0xec
12 002af3f0 708f0664 system_runtime_serialization_ni!System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(System.Runtime.Serialization.XmlReaderDelegator, Boolean, System.Runtime.Serialization.DataContractResolver)+0x57
13 002af40c 002e0127 system_runtime_serialization_ni!System.Runtime.Serialization.DataContractSerializer.ReadObject(System.Xml.XmlReader)+0x2c
14 002af478 74af2552 version1!Version1.Program.Main(System.String[])+0xd7
15 002af484 74aff237 clr!CallDescrWorkerInternal+0x34
16 002af4d8 74afff60 clr!CallDescrWorkerWithHandler+0x6b
17 002af550 74c1671c clr!MethodDescCallSite::CallTargetWorker+0x152
18 (Inline) -------- clr!MethodDescCallSite::Call+0xf
19 002af674 74c16840 clr!RunMain+0x1aa
1a 002af8e8 74c53dc5 clr!Assembly::ExecuteMainMethod+0x124
1b 002afde8 74c53e68 clr!SystemDomain::ExecuteMainMethod+0x63c
1c 002afe40 74c53f7a clr!ExecuteEXE+0x4c
1d 002afe80 74c56b86 clr!_CorExeMainInternal+0xdc
1e 002afebc 7519ffcc clr!_CorExeMain+0x4d
1f 002afef8 75217f16 mscoreei!_CorExeMain+0x10a
20 002aff08 75214de3 mscoree!ShellShim__CorExeMain+0x99
21 002aff10 7628336a mscoree!_CorExeMain_Exported+0x8
22 002aff1c 77a59882 kernel32!BaseThreadInitThunk+0xe
23 002aff5c 77a59855 ntdll!__RtlUserThreadStart+0x70
24 002aff74 00000000 ntdll!_RtlUserThreadStart+0x1b
Exception type: System.Runtime.Serialization.SerializationException
Message: Element AnotherPerson from namespace Tests.DataContract cannot have child contents to be deserialized as an object. Please use XmlNode[] to deserialize this pattern of XML.
InnerException:
Exception type: System.Xml.XmlException
Message: 'Element' is an invalid XmlNodeType.
StackTrace:
system_xml_ni!System.Xml.XmlReader.ReadEndElement()+0x55d416
system_runtime_serialization_ni!System.Runtime.Serialization.XmlReaderDelegator.ReadEndElement()+0x18
system_runtime_serialization_ni!System.Runtime.Serialization.ObjectDataContract.ReadXmlValue(System.Runtime.Serialization.XmlReaderDelegator, System.Runtime.Serialization.XmlObjectSerializerReadContext)
Source code
public int GetMemberIndex(XmlReaderDelegator xmlReader, XmlDictionaryString[] memberNames, XmlDictionaryString[] memberNamespaces, int memberIndex, ExtensionDataObject extensionData)
{
for (int i = memberIndex + 1; i < memberNames.Length; i++)
{
if (xmlReader.IsStartElement(memberNames[i], memberNamespaces[i]))
return i;
}
HandleMemberNotFound(xmlReader, extensionData, memberIndex); <--------------------------
return memberNames.Length;
}
protected void HandleMemberNotFound(XmlReaderDelegator xmlReader, ExtensionDataObject extensionData, int memberIndex)
{
xmlReader.MoveToContent();
if (xmlReader.NodeType != XmlNodeType.Element)
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateUnexpectedStateException(XmlNodeType.Element, xmlReader));
if (IgnoreExtensionDataObject || extensionData == null)
SkipUnknownElement(xmlReader);
else
HandleUnknownElement(xmlReader, extensionData, memberIndex); <---------------------------
}
internal void HandleUnknownElement(XmlReaderDelegator xmlReader, ExtensionDataObject extensionData, int memberIndex)
{
if (extensionData.Members == null)
extensionData.Members = new List<ExtensionDataMember>();
extensionData.Members.Add(ReadExtensionDataMember(xmlReader, memberIndex)); <-------------------------
}
ExtensionDataMember ReadExtensionDataMember(XmlReaderDelegator xmlReader, int memberIndex)
{
ExtensionDataMember member = new ExtensionDataMember();
member.Name = xmlReader.LocalName;
member.Namespace = xmlReader.NamespaceURI;
member.MemberIndex = memberIndex;
if (xmlReader.UnderlyingExtensionDataReader != null)
{
// no need to re-read extension data structure
member.Value = xmlReader.UnderlyingExtensionDataReader.GetCurrentNode();
}
else
member.Value = ReadExtensionDataValue(xmlReader); <----------------------------------
return member;
}
Hopefully, this design limitation of DataContract de-serialization for cyclic reference objects will be addressed in near future.
I hope this helps!