Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Having Fun with the XmlSchemaProvider: Serializing Object Trees

4.85/5 (11 votes)
9 May 20063 min read 5   532  
Learn how to serialize object trees into SOAP and back again

Introduction

About a year ago, I became very enthusiastic. I found a new attribute in the framework which allows me to determine the WSDL contract which describes my objects. Used in conjunction with the IXmlSerializable interface, I can also make sure the serializer pumps out the correct XML, corresponding to the custom XML-Schema inside the WSDL. Why is this so important? The default XmlSerializer requires all fields to have public get / set properties. This means my Product object needs a public get/set ID property. I personally didn’t want a settable ID property, because it is the never-changing primary key for that object. Furthermore, I did not want to change the public interface of my objects just because they’ll be serialized into a SOAP message. Using XmlSchemaProvider with IXmlSerializable, you can fix all this; keep only the getter, and still serialize the ID.

Now, all of this sounds good of course, and there are various samples on the internet showing how you can use these two classes to serialize your own ‘Product’ objects. However, the examples I found only show how to serialize a single object, not a tree of objects, such as an Order-OrderItem combination or Order-Customer-Address. Is this more difficult then? Well, yeah, a tad-bit, because you’ll have to share the XML-Schema definition somehow.

Not finding the answer on the web, I dug into this problem myself, like I did about a year ago. The only difference is that this time, I have a working solution which I want to share with you guys.

Serializing an Order Object

The following example will show how you can utilize XmlSchemaProvider for serializing an object-tree. This example uses two objects, Order and Product, with an Order containing multiple Products. The Product class contains a readonly ID property, and has a private default constructor. The Order class contains a readonly List<T> of Products and also a readonly ID property. Here, I’ll give attention only to the Order class, the Product class is not that interesting after reading up on the subject. Check the code to see how it has been built.

The Order class I’ll describe a bit further, since that’s where the magic happens.

The basic class definition for Order is as follows:

C#
[XmlSchemaProvider("GetOrderSchema")]
public class Order : IXmlSerializable
{
    int _id;
    List<Product> _products = null;

    public int ID { get;}
    public List<Product> Products { get;}
}

To supply the correct schema to the serialization infrastructure, the XmlSchemaProvider attribute is applied to the class. Inside the method tied to the XmlSchemaProvider attribute, the schema for the Order class is constructed using the System.Xml object model, also the Product is included in the schema for the Order class using an XmlSchemaInclude:

C#
public static XmlSchemaComplexType GetOrderSchema(
              XmlSchemaSet schemas)
{
    string tns = "http://diveinit.nl/schemas/test";
    string xmlns = "http://www.w3.org/2001/XMLSchema";

    XmlSchema schema = new XmlSchema();
    // Include the product schema as an include so it can
    // be referenced in the order schema.
    XmlSchema productSchema = Product.Schema;
    XmlSchemaInclude productSchemainclude =
        new XmlSchemaInclude();
    productSchemaInclude.Schema = productSchema;
    schema.Includes.Add(productSchemaInclude);
    ...
    ...
  //-- The rest of the method is ommited for brevity--//
}

Now that the WSDL has been set up correctly, it is time to serialize and deserialize the Order class. This is done using the IXmlSerializable interface, which requires you to build three methods. First up is the serialization using the WriteXml method. This method is also responsible for serializing the Product objects contained in the Order.

C#
void IXmlSerializable.WriteXml(XmlWriter writer)
{
    string ns = "http://diveinit.nl/schemas/test";
    writer.WriteElementString("id", ns, _id.ToString());
    ICollection<Product> products = Products;
    if (products.Count > 0)
    {
        writer.WriteStartElement("ProductCollection");
        foreach (Product product in products)
        {
            writer.WriteStartElement("Product");
            ((IXmlSerializable)product).WriteXml(writer);
            writer.WriteEndElement();
        }
        writer.WriteEndElement();
    }
}

The root element is supplied for you, this means you don’t have to write this yourself inside the WriteXml method. So the first item to write is the private ID field of the Order object. Next up are all the Products contained in the Order. They are contained inside an extra element called "ProductCollection". Make sure to use the WriteXml method of the Product object so that is serialized correctly too.

Now for some deserialization. This is a bit more difficult because you do not know how many Products the Order contains, when deserializing. The XmlReader provides a handy method to determine this, which is called IsStartElement.

C#
void IXmlSerializable.ReadXml(XmlReader reader)
{
    string ns = "http://diveinit.nl/schemas/test";
    reader.ReadStartElement();
    _id = reader.ReadElementContentAsInt("id", ns);
    if (reader.IsStartElement("ProductCollection", ns))
    {
        reader.ReadStartElement();
        while (reader.IsStartElement("Product", ns))
        {
            Product product = new Product();
            ((IXmlSerializable)product).ReadXml(reader);
            Products.Add(product);
        }
    }
    reader.ReadEndElement();
}

That is it. A fully functional example on how to use XmlSchemaProvider in conjunction with IXmlSerializable for all your serialization needs!

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.