XML serialise a class with circular references using XmlSerializer

J

jpalmer

Hi,

I’m setting up a .NET 2.0 c# WebService. One of the web methods requires a
complex parameter type which contains circular references, so by default
XmlSerializer can not serialise and deserialise the .NET object. I.e The
circular type looks something like this:

1 public class A
2 {
3 public A()
4 {
5
6 }
7
8 public A(int id)
9 {
10 this.Id = id;
11 }
12 public int Id;
13 public B B;
14 }
15
16 public class B
17 {
18 public B()
19 {
20
21 }
22 public B(int id)
23 {
24 this.Id = id;
25 }
26 public int Id;
27 public A A;
28 }
29


Now I can easily implement IXmlSerializable.WriteXML to write the XML to a
form the deals with the circular reference. i.e.

1 using System;
2 using System.IO;
3 using System.Xml;
4 using System.Xml.Serialization;
5
6 public class A : IXmlSerializable
7 {
8 public A()
9 {
10
11 }
12 public A(int id)
13 {
14 this.Id = id;
15 }
16 public int Id;
17 public B B;
18 public void WriteXml(XmlWriter w)
19 {
20 w.WriteStartElement("A");
21 w.WriteElementString("Id", Id.ToString());
22 B.WriteXml(w);
23 w.WriteEndElement();
24 }
25 public void ReadXml(XmlReader r)
26 {
27 throw new NotImplementedException();
28 }
29
30 public System.Xml.Schema.XmlSchema GetSchema()
31 {
32 throw new NotImplementedException();
33 }
34 }
35
36 public class B : IXmlSerializable
37 {
38 public B()
39 {
40
41 }
42
43 public B(int id)
44 {
45 this.Id = id;
46 }
47
48 public int Id;
49 public A A;
50 public void WriteXml(XmlWriter w)
51 {
52 w.WriteStartElement("B");
53 w.WriteElementString("Id", Id.ToString());
54 w.WriteStartElement("A");
55 w.WriteAttributeString("RefID", A.Id.ToString());
56 w.WriteEndElement();
57 w.WriteEndElement();
58 }
59 public void ReadXml(XmlReader r)
60 {
61 throw new NotImplementedException();
62 }
63 public System.Xml.Schema.XmlSchema GetSchema()
64 {
65 throw new NotImplementedException();
66 }
67 }


This produces output like this:

<?xml version="1.0" encoding="utf-8" ?>
<A xmlns="http://something.com/">
<A>
<Id>1</Id>
<B>
<Id>2</Id>
<A RefID="1" />
</B>
</A>
</A>

But how do I deserialise the XML stream and set the correct object
reference, making sure that it is done once the object tree is fully read?

With the System.Runtime.Serialization framework I could do with by fixing
the object references after the object tree was read by implementing the
IDeserializationCallback.OnDeserialization(Object sender) method. But there
does not seem to be anything similar for the System.Xml.Serialization
framework.

Any help would be greatly appreciated.

Cheers,
Jeremy
 
P

Pavel Minaev

I’m setting up a .NET 2.0 c# WebService.  One of the web methods requires a
complex parameter type which contains circular references, so by default
XmlSerializer can not serialise and deserialise the .NET object. I.e The
circular type looks something like this:

1       public class A
2       {
3           public A()
4           {
5        
6           }
7        
8           public A(int id)
9           {
10              this.Id = id;
11          }
12          public int Id;
13          public B B;
14      }
15      
16      public class B
17      {
18          public B()
19          {
20      
21          }
22          public B(int id)
23          {
24              this.Id = id;
25          }
26          public int Id;
27          public A A;
28      }
29      

Now I can easily implement IXmlSerializable.WriteXML to write the XML to a
form the deals with the circular reference. i.e.

1       using System;
2       using System.IO;
3       using System.Xml;
4       using System.Xml.Serialization;
5        
6       public class A : IXmlSerializable
7       {
8           public A()
9           {
10      
11          }
12          public A(int id)
13          {
14              this.Id = id;
15          }
16          public int Id;
17          public B B;
18          public void WriteXml(XmlWriter w)
19          {
20              w.WriteStartElement("A");
21              w.WriteElementString("Id", Id.ToString());
22              B.WriteXml(w);
23              w.WriteEndElement();
24          }
25          public void ReadXml(XmlReader r)
26          {
27              throw new NotImplementedException();
28          }
29      
30          public System.Xml.Schema.XmlSchema GetSchema()
31          {
32              throw new NotImplementedException();
33          }
34      }
35      
36      public class B : IXmlSerializable
37      {
38          public B()
39          {
40      
41          }
42      
43          public B(int id)
44          {
45              this.Id = id;
46          }
47      
48          public int Id;
49          public A A;
50          public void WriteXml(XmlWriter w)
51          {
52              w.WriteStartElement("B");
53              w.WriteElementString("Id", Id.ToString());
54              w.WriteStartElement("A");
55              w.WriteAttributeString("RefID", A.Id.ToString());
56              w.WriteEndElement();
57              w.WriteEndElement();
58          }
59          public void ReadXml(XmlReader r)
60          {
61              throw new NotImplementedException();
62          }
63          public System.Xml.Schema.XmlSchema GetSchema()
64          {
65              throw new NotImplementedException();
66          }
67      }

This produces output like this:

<?xml version="1.0" encoding="utf-8" ?>  
<A xmlns="http://something.com/">
    <A>
        <Id>1</Id>  
        <B>
            <Id>2</Id>  
            <A RefID="1" />  
        </B>
    </A>
</A>

But how do I deserialise the XML stream and set the correct object
reference, making sure that it is done once the object tree is fully read?

With the System.Runtime.Serialization framework I could do with by fixing
the object references after the object tree was read by implementing the
IDeserializationCallback.OnDeserialization(Object sender) method. But there
does not seem to be anything similar for the System.Xml.Serialization
framework.

XmlSerializer framework provides no way of doing that. It is really
geared towards serializing trees of specifically designed classes, and
not arbitrary graphs of domain objects. There is no OnDeserialization
or anything similar, either - you can still introduce the patch-up
code, of course, but you'll have to call it yourself after
deserialization.
 
N

not_a_commie

Push your company into using WCF. Then you can use the
DataContractSerializer, which doesn't have this problem. And it works
much better in general. And you can do binary transfers on the LAN
with text/SOAP transfers on the WAN.
 
P

Pavel Minaev

Push your company into using WCF. Then you can use the
DataContractSerializer, which doesn't have this problem. And it works
much better in general.

That's all well and good, but if they already have an existing
contract for the Web service with the schema as outlined above, then
DataContractSerializer likely won't help them - it doesn't support
mapping to XML attributes, for one thing.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top