XML Deserialization (IXmlSerializable implementation)

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • John Glover

    XML Deserialization (IXmlSerializable implementation)

    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 IXmlSerializabl e.

    I called it XmlHashtable. It has, among other convenience methods, a method
    called ReadXml(XmlRead er) and a method called WriteXml(XmlWri ter). 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, IXmlSerializabl e {
    /// <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="XmlHashta ble"/> from its XML representation.
    /// </summary>
    /// <param name="reader">T he <see cref="XmlReader "/> from which the
    /// object is deserialized</param>
    public void ReadXml ( System.Xml.XmlR eader reader ) {
    try {
    string key = null;
    object value = null;
    Type type = null;

    reader.ReadToFo llowing ( LocalName );
    if ( reader.IsStartE lement ( LocalName ) ) {
    reader.ReadStar tElement ( LocalName );

    while ( reader.NodeType != XmlNodeType.End Element ) {
    key = reader.GetAttri bute ( "Key" );
    type = Type.GetType ( reader.GetAttri bute ( "Type" ) );
    reader.ReadStar tElement ( ItemType );
    reader.ReadStar tElement ( "Value" );
    XmlSerializer serializer = new XmlSerializer ( type );
    value = serializer.Dese rialize ( reader.ReadSubt ree( ) );
    this.Add ( key, value );
    reader.ReadEndE lement ( ); // Reading end node for Name
    reader.ReadEndE lement ( ); // Reading end node for Value
    reader.ReadEndE lement ( ); // Reading end node for
    Element
    reader.MoveToCo ntent ( );
    }

    reader.ReadEndE lement ( ); // Reading end root node
    }
    }

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

    /// <summary>
    /// Converts an <see cref="XmlHashta ble"/> object into its XML
    /// representation.
    /// </summary>
    /// <param name="writer">T he <see cref="XmlWriter "/> stream to which the
    /// object is serialized</param>
    public void WriteXml ( System.Xml.XmlW riter writer ) {
    writer.WriteSta rtElement ( LocalName );
    foreach ( DictionaryEntry de in this ) {
    writer.WriteSta rtElement ( ItemType );
    writer.WriteAtt ributeString ( "Key", de.Key.ToString ( ) );
    writer.WriteAtt ributeString ( "Type", de.Value.GetTyp e (
    ).FullName );
    writer.WriteSta rtElement ( "Value" );
    XmlSerializer serializer = new XmlSerializer ( de.Value.GetTyp e
    ( ) );
    serializer.Seri alize ( writer, de.Value );
    writer.WriteEnd Element ( );
    writer.WriteEnd Element ( );
    }

    writer.WriteEnd Element ( );
    }
    }
    </code>

    The inheriting classes must use the [XmlSchemaProvid er( "GetXmlSche ma" )]
    attribute, which they do as shown here:

    <code>
    /// <summary>
    /// Adds the XML schema to the <see cref="XmlSchema Set"/> and returns
    /// the fully qualified name for the <see cref="AssetProp erties"/>
    /// root element.
    /// </summary>
    /// <param name="set">The <see cref="XmlSchema Set"/> to add the schema
    /// to</param>
    /// <returns><see cref="XmlQualif iedName"/> representing the
    /// schema for this type</returns>
    public static XmlQualifiedNam e GetXmlSchema ( XmlSchemaSet set ) {
    XmlSchemaAttrib ute attribute;
    XmlSchemaSequen ce sequence;
    XmlSchemaElemen t element;

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

    // create the "Key" attribute and add it to the complex item type
    attribute = new XmlSchemaAttrib ute ( );
    attribute.Name = "Key";
    attribute.Schem aTypeName = new XmlQualifiedNam e ( "string",
    "http://www.w3.org/2001/XMLSchema" );
    attribute.Use = XmlSchemaUse.Re quired;
    itemType.Attrib utes.Add ( attribute );

    // create the "Type" attribute and add it to the complex item type
    attribute = new XmlSchemaAttrib ute ( );
    attribute.Name = "Type";
    attribute.Schem aTypeName = new XmlQualifiedNam e ( "string",
    "http://www.w3.org/2001/XMLSchema" );
    attribute.Use = XmlSchemaUse.Re quired;
    itemType.Attrib utes.Add ( attribute );

    sequence = new XmlSchemaSequen ce ( );

    element = new XmlSchemaElemen t ( );
    element.Name = "Value";
    element.SchemaT ypeName = new XmlQualifiedNam e ( "anyType",
    "http://www.w3.org/2001/XMLSchema" );

    sequence.Items. Add ( element );
    itemType.Partic le = sequence;

    XmlSchemaComple xType assetProperties Type = new
    XmlSchemaComple xType ( );
    assetProperties Type.Name = LocalName + "Type";

    // create "AssetPropertie sType" schema type for
    "AssetPropertie s" element
    sequence = new XmlSchemaSequen ce ( );
    element = new XmlSchemaElemen t ( );
    element.Name = XmlCoreTypes.As setProperties.I temType;
    element.MaxOccu rsString = "unbounded" ;
    element.MinOccu rsString = "0";
    element.SchemaT ypeName = new XmlQualifiedNam e ( itemType.Name,
    "http://mediaframe.com" );
    sequence.Items. Add ( element );
    assetProperties Type.Particle = sequence;

    element = new XmlSchemaElemen t ( );
    element.Name = LocalName;
    element.SchemaT ypeName = new XmlQualifiedNam e (
    assetProperties Type.Name, "http://mediaframe.com" );

    // Generate the Schema element itself and set the necessary
    properties
    XmlSchema schema = new XmlSchema ( );
    schema.TargetNa mespace = "http://mediaframe.com" ;
    schema.Namespac es.Add ( "mf", "http://mediaframe.com" );
    schema.Items.Ad d ( element );
    schema.Items.Ad d ( itemType );
    schema.Items.Ad d ( assetProperties Type );

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

    //return element.Qualifi edName;
    return assetProperties Type.QualifiedN ame;
    }
    </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
  • Kevin Yu [MSFT]

    #2
    RE: XML Deserialization (IXmlSerializab le implementation)

    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."

    Comment

    • John Glover

      #3
      RE: XML Deserialization (IXmlSerializab le implementation)

      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 XmlArraySeriali zationTest ( ) {
      AssetProperties[ ] objA = new AssetProperties[ ] {
      <put some code here to initialize the array> };

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

      AssetProperties[ ] objB = (AssetPropertie s[ ])serializer.Des erialize (
      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(XmlRead er) method so that it read an
      extra end element at the end, like this:

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

      reader.ReadToFo llowing ( LocalName );
      if ( reader.IsStartE lement ( LocalName ) ) {
      reader.ReadStar tElement ( LocalName );

      while ( reader.NodeType != XmlNodeType.End Element ) {
      key = reader.GetAttri bute ( "Key" );
      type = Type.GetType ( reader.GetAttri bute ( "Type" ) );
      reader.ReadStar tElement ( ItemType );
      reader.ReadStar tElement ( "Value" );
      XmlSerializer serializer = new XmlSerializer ( type );
      value = serializer.Dese rialize ( reader.ReadSubt ree( ) );
      this.Add ( key, value );
      reader.ReadEndE lement ( ); // Reading end node for node inside Value
      reader.ReadEndE lement ( ); // Reading end node for Value
      reader.ReadEndE lement ( ); // Reading end node for Property
      reader.MoveToCo ntent ( );
      }
      reader.ReadEndE lement( ); // Reading end node for Element
      reader.ReadEndE lement( ); // 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="AssetProp erties" type="tmf:Asset PropertiesType" />

      <xs:complexTy pe name="PropertyT ype">
      <xs:sequence>
      <xs:element name="Value" type="xs:anyTyp e" />
      </xs:sequence>
      <xs:attribute name="Key" type="xs:string " use="required" />
      <xs:attribute name="Type" type="xs:string " use="required" />
      </xs:complexType>

      <xs:complexTy pe name="AssetProp ertiesType">
      <xs:sequence>
      <xs:element minOccurs="0" maxOccurs="unbo unded" name="Property"
      type="tmf:Prope rtyType" />
      </xs:sequence>
      </xs:complexType>

      </xs:schema>

      So I would have assumed that the root element would be a <AssetPropertie s>,
      but it is not. The root element is <AssetPropertie sType>. 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"
      attributeFormDe fault="unqualif ied" elementFormDefa ult="qualified"
      targetNamespace ="http://thomson.com/MediaFrame"
      xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="AssetProp erties">
      <xs:complexType >
      <xs:sequence>
      <xs:element maxOccurs="unbo unded" 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:
      [color=blue]
      > 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."
      >
      >[/color]

      Comment

      • Kevin Yu [MSFT]

        #4
        RE: XML Deserialization (IXmlSerializab le implementation)

        Hi John,

        Have you tried to use XmlRootAttribut e for the AssetProperties class?

        [XmlRoot(Namespa ce = "www.contoso.co m",
        ElementName = "AsseteProperti es",
        DataType = "string",
        IsNullable=true )]

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


        frlrfsystemxmls erializationxml rootattributecl asstopic.asp

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

        Comment

        Working...