By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
435,064 Members | 1,445 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 435,064 IT Pros & Developers. It's quick & easy.

XML Deserialization (IXmlSerializable implementation)

P: n/a
To whoever can help,

I've been having a problem with XML deserialization lately.

I needed to serialize and deserialze two objects which inherited from
Hashtable. Because IDictionary implementations cannot be serialized, I had
to take matters into my own hands by implementing a wrapper over the
Hashtable which implemted IXmlSerializable.

I called it XmlHashtable. It has, among other convenience methods, a method
called ReadXml(XmlReader) and a method called WriteXml(XmlWriter). This is
an abstract class which also contains two abstract properties which return
strings so I could have inheriting classes choose the names they wish for the
root element name and the name for individual items.

I implemented my own, because though I've seen code on the web which is
similar, I needed to be able to use actual objects as values, and not just
strings. This code works with any Type you wish to put into the Hashtable
that is available at runtime. I've tested it with both native Types and some
of my user defined types.

The code looks like this:
<code>
[XmlType( "XmlDict" )]
public abstract class XmlHashtable : Hashtable, IXmlSerializable {
/// <summary>
/// Returns the local name of the root node for the class. Implementing
/// classes must override this property.
/// </summary>
protected abstract string LocalName {get;}
/// <summary>
/// Returns the local name of the name of items in the dictionary.
/// Implementing classes must override this property.
/// </summary>
protected abstract string ItemType { get;}
/// <summary>
/// Generates an <see cref="XmlHashtable"/> from its XML representation.
/// </summary>
/// <param name="reader">The <see cref="XmlReader"/> from which the
/// object is deserialized</param>
public void ReadXml ( System.Xml.XmlReader reader ) {
try {
string key = null;
object value = null;
Type type = null;

reader.ReadToFollowing ( LocalName );
if ( reader.IsStartElement ( LocalName ) ) {
reader.ReadStartElement ( LocalName );

while ( reader.NodeType != XmlNodeType.EndElement ) {
key = reader.GetAttribute ( "Key" );
type = Type.GetType ( reader.GetAttribute ( "Type" ) );
reader.ReadStartElement ( ItemType );
reader.ReadStartElement ( "Value" );
XmlSerializer serializer = new XmlSerializer ( type );
value = serializer.Deserialize ( reader.ReadSubtree( ) );
this.Add ( key, value );
reader.ReadEndElement ( ); // Reading end node for Name
reader.ReadEndElement ( ); // Reading end node for Value
reader.ReadEndElement ( ); // Reading end node for
Element
reader.MoveToContent ( );
}

reader.ReadEndElement ( ); // Reading end root node
}
}

catch ( XmlException e ) {
throw new XmlException ( "XmlDictionary.ReadXml(XmlReader)
reader pointing to invalid element", e );
}
}

/// <summary>
/// Converts an <see cref="XmlHashtable"/> object into its XML
/// representation.
/// </summary>
/// <param name="writer">The <see cref="XmlWriter"/> stream to which the
/// object is serialized</param>
public void WriteXml ( System.Xml.XmlWriter writer ) {
writer.WriteStartElement ( LocalName );
foreach ( DictionaryEntry de in this ) {
writer.WriteStartElement ( ItemType );
writer.WriteAttributeString ( "Key", de.Key.ToString ( ) );
writer.WriteAttributeString ( "Type", de.Value.GetType (
).FullName );
writer.WriteStartElement ( "Value" );
XmlSerializer serializer = new XmlSerializer ( de.Value.GetType
( ) );
serializer.Serialize ( writer, de.Value );
writer.WriteEndElement ( );
writer.WriteEndElement ( );
}

writer.WriteEndElement ( );
}
}
</code>

The inheriting classes must use the [XmlSchemaProvider( "GetXmlSchema" )]
attribute, which they do as shown here:

<code>
/// <summary>
/// Adds the XML schema to the <see cref="XmlSchemaSet"/> and returns
/// the fully qualified name for the <see cref="AssetProperties"/>
/// root element.
/// </summary>
/// <param name="set">The <see cref="XmlSchemaSet"/> to add the schema
/// to</param>
/// <returns><see cref="XmlQualifiedName"/> representing the
/// schema for this type</returns>
public static XmlQualifiedName GetXmlSchema ( XmlSchemaSet set ) {
XmlSchemaAttribute attribute;
XmlSchemaSequence sequence;
XmlSchemaElement element;

XmlSchemaComplexType itemType = new XmlSchemaComplexType ( );
itemType.Name = ItemType + "Type";

// create the "Key" attribute and add it to the complex item type
attribute = new XmlSchemaAttribute ( );
attribute.Name = "Key";
attribute.SchemaTypeName = new XmlQualifiedName ( "string",
"http://www.w3.org/2001/XMLSchema" );
attribute.Use = XmlSchemaUse.Required;
itemType.Attributes.Add ( attribute );

// create the "Type" attribute and add it to the complex item type
attribute = new XmlSchemaAttribute ( );
attribute.Name = "Type";
attribute.SchemaTypeName = new XmlQualifiedName ( "string",
"http://www.w3.org/2001/XMLSchema" );
attribute.Use = XmlSchemaUse.Required;
itemType.Attributes.Add ( attribute );

sequence = new XmlSchemaSequence ( );

element = new XmlSchemaElement ( );
element.Name = "Value";
element.SchemaTypeName = new XmlQualifiedName ( "anyType",
"http://www.w3.org/2001/XMLSchema" );

sequence.Items.Add ( element );
itemType.Particle = sequence;

XmlSchemaComplexType assetPropertiesType = new
XmlSchemaComplexType ( );
assetPropertiesType.Name = LocalName + "Type";

// create "AssetPropertiesType" schema type for
"AssetProperties" element
sequence = new XmlSchemaSequence ( );
element = new XmlSchemaElement ( );
element.Name = XmlCoreTypes.AssetProperties.ItemType;
element.MaxOccursString = "unbounded";
element.MinOccursString = "0";
element.SchemaTypeName = new XmlQualifiedName ( itemType.Name,
"http://mediaframe.com" );
sequence.Items.Add ( element );
assetPropertiesType.Particle = sequence;

element = new XmlSchemaElement ( );
element.Name = LocalName;
element.SchemaTypeName = new XmlQualifiedName (
assetPropertiesType.Name, "http://mediaframe.com" );

// Generate the Schema element itself and set the necessary
properties
XmlSchema schema = new XmlSchema ( );
schema.TargetNamespace = "http://mediaframe.com";
schema.Namespaces.Add ( "mf", "http://mediaframe.com" );
schema.Items.Add ( element );
schema.Items.Add ( itemType );
schema.Items.Add ( assetPropertiesType );

// Add the schema to the set of schemas and compile it
set.XmlResolver = new XmlUrlResolver ( );
set.Add ( schema );
set.Compile ( );

//return element.QualifiedName;
return assetPropertiesType.QualifiedName;
}
</code>

So, serializing and deserializing one of these objects by itself works
perfectly. However, when I try to serialize an array of these objects and
then deserialize it, I only get the first one deserialized before it all
quits on me. The serialization works fine, I can see the XML by putting a
break point before deserializing.

I'm trying to use this in my web services, but it has the same issue whether
I serialize and deserialize manually (with the XmlSerializer) or use the web
services to do it all.

I'm just trying to figure out where I went wrong. Any tips would be
extremely useful.

Thanks,
John Glover
Mar 21 '06 #1
Share this Question
Share on Google+
3 Replies


P: n/a
Hi John,

Could you show me how you serialize and deserialize the array? I'm trying
to make a repro on my computer so that I can make a better troubleshooting.
Thanks!

Kevin Yu
=======
"This posting is provided "AS IS" with no warranties, and confers no
rights."

Mar 22 '06 #2

P: n/a
Kevin,

I'm using the following code to serialize and deserialize which is pulled
from my test class for an object called AssetProperties which implements the
XmlHashtable abstract class from my first post:

<code>
[TestMethod ( )]
public void XmlArraySerializationTest ( ) {
AssetProperties[ ] objA = new AssetProperties[ ] {
<put some code here to initialize the array> };

XmlSerializer serializer = new XmlSerializer ( typeof ( AssetProperties[
] ) );
StringWriter writer = new StringWriter ( );
serializer.Serialize ( writer, objA );
StringReader reader = new StringReader ( writer.ToString ( ) );

AssetProperties[ ] objB = (AssetProperties[ ])serializer.Deserialize (
reader );

Assert.AreEqual ( objA.Length, objB.Length,
objA.GetType ( ).ToString ( ) + " failed XML serialization and
deserialization -- arrays not the same size." );
for ( int i = 0; i < objA.Length; ++i ) {
Assert.AreEqual ( objA[i], objB[i], objA.GetType ( ).ToString ( ) +
" failed XML serialization and deserialization." );
}
}
</code>

However, I think I have figured out what the problem is. Well, at least I
know what to do to make it stop, but I still have a problem. I fixed the
problem by adding a line to my ReadXml(XmlReader) method so that it read an
extra end element at the end, like this:

<code>
/// <summary>
/// Generates an <see cref="XmlHashtable"/> from its XML representation.
/// </summary>
/// <param name="reader">The <see cref="XmlReader"/> from which the
/// object is deserialized</param>
public void ReadXml ( System.Xml.XmlReader reader ) {
try {
string key = null;
object value = null;
Type type = null;

reader.ReadToFollowing ( LocalName );
if ( reader.IsStartElement ( LocalName ) ) {
reader.ReadStartElement ( LocalName );

while ( reader.NodeType != XmlNodeType.EndElement ) {
key = reader.GetAttribute ( "Key" );
type = Type.GetType ( reader.GetAttribute ( "Type" ) );
reader.ReadStartElement ( ItemType );
reader.ReadStartElement ( "Value" );
XmlSerializer serializer = new XmlSerializer ( type );
value = serializer.Deserialize ( reader.ReadSubtree( ) );
this.Add ( key, value );
reader.ReadEndElement ( ); // Reading end node for node inside Value
reader.ReadEndElement ( ); // Reading end node for Value
reader.ReadEndElement ( ); // Reading end node for Property
reader.MoveToContent ( );
}
reader.ReadEndElement( ); // Reading end node for Element
reader.ReadEndElement( ); // Reading extra funny end node
</code>

But the problem still remains that I have this funny root element. I
believe the problem is that my schema is not turning out the way I would
like. The schema looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:tmf="http://thomson.com/MediaFrame"
targetNamespace="http://thomson.com/MediaFrame"
xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="AssetProperties" type="tmf:AssetPropertiesType" />

<xs:complexType name="PropertyType">
<xs:sequence>
<xs:element name="Value" type="xs:anyType" />
</xs:sequence>
<xs:attribute name="Key" type="xs:string" use="required" />
<xs:attribute name="Type" type="xs:string" use="required" />
</xs:complexType>

<xs:complexType name="AssetPropertiesType">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="Property"
type="tmf:PropertyType" />
</xs:sequence>
</xs:complexType>

</xs:schema>

So I would have assumed that the root element would be a <AssetProperties>,
but it is not. The root element is <AssetPropertiesType>. This is not what
I expect.

When I manipulate the code to build a schema like this:

<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns:tns="http://thomson.com/MediaFrame"
attributeFormDefault="unqualified" elementFormDefault="qualified"
targetNamespace="http://thomson.com/MediaFrame"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="AssetProperties">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="Property">
<xs:complexType>
<xs:sequence>
<xs:element name="Value" type="xs:string" />
</xs:sequence>
<xs:attribute name="Key" type="xs:string" use="required" />
<xs:attribute name="Type" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

I get all kinds of errors about a name being required.

Is there a good reference for how to build an XML schema using code from the
XmlSchema namespace?

Any suggestions about what I've done wrong would be extremely helpful.

Thanks,
John

"Kevin Yu [MSFT]" wrote:
Hi John,

Could you show me how you serialize and deserialize the array? I'm trying
to make a repro on my computer so that I can make a better troubleshooting.
Thanks!

Kevin Yu
=======
"This posting is provided "AS IS" with no warranties, and confers no
rights."

Mar 22 '06 #3

P: n/a
Hi John,

Have you tried to use XmlRootAttribute for the AssetProperties class?

[XmlRoot(Namespace = "www.contoso.com",
ElementName = "AsseteProperties",
DataType = "string",
IsNullable=true)]

I think this will help you to control the root element name. For more
information, please check the following link:

http://msdn.microsoft.com/library/de...us/cpref/html/
frlrfsystemxmlserializationxmlrootattributeclassto pic.asp

Kevin Yu
=======
"This posting is provided "AS IS" with no warranties, and confers no
rights."

Mar 24 '06 #4

This discussion thread is closed

Replies have been disabled for this discussion.