Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

XmlSerializer and shared objects (or How to generate IDREFs using XmlSerializer)

0 views
Skip to first unread message

Stuart Robertson

unread,
Jul 15, 2003, 5:05:58 AM7/15/03
to
I am trying to find a solution that will allow me to use XmlSerializer to serialize/deserialize a collection of objects where a given object is shared between two or more other objects, and not create duplicate XML representations of the shared object, but instead use IDREFs to refer to the shared object.
 
The XML I'm trying to produce is as follows (where "href" is an IDREF):
<?xml version="1.0" encoding="utf-8"?>
<MyRootClass xmlns:xsd="
http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <BusinessEvent Id="1" DisplayName="A Test Event" />
  <Conditions>
    <EventCondition Id="2" DisplayName="Condition 1">
      <TheEvent href="1"/>
    </EventCondition>
    <EventCondition Id="3" DisplayName="Condition 2">
      <TheEvent href="1"/>
    </EventCondition>
  </Conditions>
</MyRootClass>
However, the XML produced by XmlSerializer is the following:
<?xml version="1.0" encoding="utf-8"?>
<MyRootClass xmlns:xsd="
http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <BusinessEvent Id="1" DisplayName="A Test Event" />
  <Conditions>
    <EventCondition Id="2" DisplayName="Condition 1">
      <TheEvent Id="1" DisplayName="A Test Event" />
    </EventCondition>
    <EventCondition Id="3" DisplayName="Condition 2">
      <TheEvent Id="1" DisplayName="A Test Event" />
    </EventCondition>
  </Conditions>
</MyRootClass>
The code used to produce the (incorrect) XML is as follows:
using System;
using System.Collections;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
 
public class TestC {
   public static void Main() {
      TestC test = new TestC();
      test.SerializeDocument("sharedEvent.xml");
   }
 
   public void SerializeDocument(string filename) {
      XmlSerializer s = new XmlSerializer(typeof(MyRootClass));
 
      using (TextWriter writer= new StreamWriter(filename)) {
         MyRootClass myRootClass = new MyRootClass();
 
         BusinessEvent evt = new BusinessEvent("1", "A Test Event");
         myRootClass.TheSharedEvent = evt;
 
         EventCondition ec1 = new EventCondition("2", "Condition 1");
         ec1.TheEvent = evt;
         myRootClass.Conditions.Add(ec1);
 
         EventCondition ec2 = new EventCondition("3", "Condition 2");
         ec2.TheEvent = evt;
         myRootClass.Conditions.Add(ec2);
 
         s.Serialize(writer, myRootClass);
      }
   }
}
 
// This is the root class that will be serialized.
public class MyRootClass {
   [XmlElement("BusinessEvent")]
   public BusinessEvent TheSharedEvent;
 
   [XmlArrayItem(IsNullable=true, Type = typeof(EventCondition))]
   [XmlArray]
   public IList Conditions = new ArrayList();
}
 
// A single instance of BusinessEvent will be shared by two EventCondition instances
public class BusinessEvent {
   [XmlAttribute(DataType="ID")]
   public string Id;
 
   [XmlAttribute]
   public string DisplayName;
 
   public BusinessEvent() {}
   public BusinessEvent(string Id, string DisplayName) {
      this.Id = Id;
      this.DisplayName = DisplayName;
   }
}
 
// Two EventCondition instances will refer to the same (single) BusinessEvent instance
public class EventCondition {
   [XmlAttribute(DataType="ID")]
   public string Id;
 
   [XmlAttribute]
   public string DisplayName;
 
   //[XmlAttribute(DataType="IDREF")]
   public BusinessEvent TheEvent;
 
   public EventCondition() {}
   public EventCondition(string Id, string DisplayName) {
      this.Id = Id;
      this.DisplayName = DisplayName;
   }
}
 
Any ideas or pointers on how to achieve the IDREF bit will be greatly appreciated.

Christoph Schittko [MVP]

unread,
Jul 15, 2003, 9:58:22 PM7/15/03
to
Stuart,
 
You can get the XmlSerializer to produce a format that's based on section 5 of the SOAP 1.1 spec [0]. However, that format serializes ALL objects in an object graph as top level objects and links them up via ID/IDREF combinations.

To force that format you have to initialize the XmlSerializer with TypeMappings from the SoapReflectionImporter as shown in the snippet below:

XmlTextWriter xw = new XmlTextWriter( "bla.xml", null );
XmlSerializer ser = new XmlSerializer(xmlType);
XmlTypeMapping xmlType =
  (new SoapReflectionImporter()).ImportTypeMapping(obj.GetType());
   ser.Serialize(xw, obj);
 
If that's not good enough for you then you pretty much have to implement serialization by hand.

--
HTH
Christoph Schittko [MVP]
Software Architect, .NET Mentor

"Stuart Robertson" <sr_...@absolutesys.com> wrote in message news:OX$VQBrSD...@tk2msftngp13.phx.gbl...

Stuart Robertson

unread,
Jul 16, 2003, 5:00:36 AM7/16/03
to
Hi Christoph,
 
Thanks for the info.  Unfortunately, I can't get the code you suggested to work.  I get an InvalidOperationException that states:
 
Unhandled Exception: System.InvalidOperationException: There was an error generating the XML document. ---> System.InvalidOperationException:  Token StartElement in state Epilog would result in an invalid XML document.
 
So I guess I need to implement a full XML persistence framework myself.... :-(
 
I would have liked to have been able to extend XmlSerializer and XmlSerializationWriter, however there is no documentation for these things... MSDN simply states "This type supports the .NET Framework infrastructure and is not intended to be used directly from your code."  Alas.  
 
<MildRant>
One wonders why Microsoft has made so many of the .NET Framework classes sealed, or declared so many methods as non-virtual, preventing extension and adaptation...?  Now I need to create duplicate much of the reflection work that XmlSerializer performs so that my XmlSerializer can use the same Xml attributes, etc. but correctly handle serialization of object graphs.
</MildRant>
Anyways, once again, thanks for the tip.
Stuart.
"Christoph Schittko [MVP]" <christophdo...@austin.rr.com> wrote in message news:eJf7C8zS...@TK2MSFTNGP11.phx.gbl...

Christoph Schittko [MVP]

unread,
Jul 16, 2003, 10:37:00 PM7/16/03
to
that doesn't sound like an error that's happening just because of the SOAP style format. Any chance your output would not result in well-formed XML? Are you writing into a fresh, new, clean XmlTextWriter ?
 
--
HTH
Christoph Schittko [MVP]
Software Architect, .NET Mentor
"Stuart Robertson" <sr_...@absolutesys.com> wrote in message news:O9aq6i3S...@TK2MSFTNGP12.phx.gbl...

Stuart Robertson

unread,
Jul 17, 2003, 2:44:41 AM7/17/03
to
Christoph,
that doesn't sound like an error that's happening just because of the SOAP style format. Any chance your output would not result in well-formed XML? Are you writing into a fresh, new, clean XmlTextWriter ?
The code I'm using is shown below (and yes, I'm using a fresh, new, clean XmlTextWriter - see the bolded text).  I'm trying this using .NET Framework 1.1, but it fails in most situations.
using System;
using System.Collections;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
 
public class TestD {
   public static void Main() {
      TestD test = new TestD();
       test.SerializeUsingSoapFormatter("sharedEvent_SOAP.xml");
   }
 
   public void SerializeUsingSoapFormatter(string filename) {
         XmlTextWriter writer = new XmlTextWriter(filename, null);
         MyRootClass myRootClass = new MyRootClass();
 
         BusinessEvent evt = new BusinessEvent("1", "A Test Event");
         myRootClass.TheSharedEvent = evt;
 
         EventCondition ec1 = new EventCondition("2", "Condition 1");
         ec1.TheEvent = evt;
         myRootClass.Conditions.Add(ec1);
 
         EventCondition ec2 = new EventCondition("3", "Condition 2");
         ec2.TheEvent = evt;
         myRootClass.Conditions.Add(ec2);
 
         XmlTypeMapping xmlTypeMapping = (new SoapReflectionImporter()).ImportTypeMapping(myRootClass.GetType());
         XmlSerializer ser = new XmlSerializer(xmlTypeMapping);
         ser.Serialize(writer, myRootClass);
   }
}
 
// This is the root class that will be serialized.
public class MyRootClass {
   [XmlElement("BusinessEvent")]
   public BusinessEvent TheSharedEvent;
 
   [XmlArrayItem(IsNullable=true, Type = typeof(EventCondition))]
   [XmlArray]
   public IList Conditions = new ArrayList();
}
 
// An instance of BusinessEvent will be shared by two EventCondition instances
public class BusinessEvent {
   public string Id;
   [XmlAttribute]
   public string DisplayName;
 
   public BusinessEvent() {}
   public BusinessEvent(string Id, string DisplayName) {
      this.Id = Id;
      this.DisplayName = DisplayName;
   }
}
 
// Two EventCondition instances will refer to the same (single) BusinessEvent instance
public class EventCondition {
   public string Id;
   [XmlAttribute]
   public string DisplayName;

Christoph Schittko [MVP]

unread,
Jul 17, 2003, 2:37:57 PM7/17/03
to
Stuart,
 
I am sorry if I wasn't clear enough. The with the Soap 1.1 Section 5 encoding, EVERY object in the object graph will be serialized as a top level element, which does not result in well-formed XML unless you enclose the serialized output in a common element. If you don't write a common parent element the underlying XmlTextWriter will complain ( throw an exception ) when you are attempting to write a 2nd top level element.
 
Everything will work once you add a line like this
 
         XmlSerializer ser = new XmlSerializer(xmlTypeMapping);
         writer.WriteStartElement( "myCommonRoot" );
         ser.Serialize(writer, myRootClass);

         writer.WriteEndElement(); // myCommonRoot
 
and then manually skip over that element when you deserialize.

--
HTH
Christoph Schittko [MVP]
Software Architect, .NET Mentor
"Stuart Robertson" <sr_...@absolutesys.com> wrote in message news:O#Olo7CTD...@tk2msftngp13.phx.gbl...

Stuart Robertson

unread,
Jul 18, 2003, 9:25:47 AM7/18/03
to
Thanks.  You learn something new everyday! :-)
0 new messages