469,621 Members | 1,802 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 469,621 developers. It's quick & easy.

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

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.
Nov 11 '05 #1
5 5197
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]" <ch********************@austin.rr.com> wrote in message news:eJ**************@TK2MSFTNGP11.phx.gbl...
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.Ge tType());
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**************@tk2msftngp13.phx.gbl...
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.
Nov 11 '05 #2
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:O9**************@TK2MSFTNGP12.phx.gbl...
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]" <ch********************@austin.rr.com> wrote in message news:eJ**************@TK2MSFTNGP11.phx.gbl...
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.Ge tType());
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**************@tk2msftngp13.phx.gbl...
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.
Nov 11 '05 #3
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(myRoot Class.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;
public BusinessEvent TheEvent;

public EventCondition() {}
public EventCondition(string Id, string DisplayName) {
this.Id = Id;
this.DisplayName = DisplayName;
}
}

Nov 11 '05 #4
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#**************@tk2msftngp13.phx.gbl...
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(myRoot Class.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;
public BusinessEvent TheEvent;

public EventCondition() {}
public EventCondition(string Id, string DisplayName) {
this.Id = Id;
this.DisplayName = DisplayName;
}
}

Nov 11 '05 #5
Thanks. You learn something new everyday! :-)
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
Nov 11 '05 #6

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

2 posts views Thread by yinjennytam | last post: by
2 posts views Thread by kedar kulkarni | last post: by
reply views Thread by swatisahasrabudhe | last post: by
1 post views Thread by smalpani | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.