Tuesday, January 19, 2010

Serialization in .NET – 5

In my previous blogs we have seen Binary serialization, XML serialization, SOAP serialization and Custom serialization using ISerilizable interface. This blog will talk about implementing custom XML serialization using the IXmlSerializable interface. We have seen in my previous blogs on serialization how to control XML and SOAP serialization using attributes. Through attributes we can take control to some extent but not to the fullest. If you want to take full control over the way your class is serialized to XML then you need to use IXmlSerializable interface.

Implementing IXmlSerializable interface is pretty straight forward. The interface has three methods namely “WriteXml (XmlWriter writer)”, “ReadXml (XmlReader reader)” and “GetSchema()”. WriteXml method is called when the object is serialized and ReadXML method is executed when the XmlSerializer deserializes the object. GetSchema method returns a XmlSchema object. As such the GetSchema method is not supposed to do anything other than returning null. The help documentation from MSDN says the following about the method.

When serializing or deserializing an object, the XmlSerializer class does not perform XML validation. For this reason, it is often safe to omit schema information by providing a trivial implementation of this method, for example by returning nullNothingnullptra null reference (Nothing in Visual Basic) (Nothing in Visual Basic).

If providing schema information is necessary, you should use the XmlSchemaProviderAttribute attribute. The GetSchema method should still return nullNothingnullptra null reference (Nothing in Visual Basic) (or Nothing).

Some .NET Framework types as well as legacy custom types implementing the IXmlSerializable interface may be using GetSchema instead of XmlSchemaProvider. In this case, the method returns an accurate XML schema that describes the XML representation of the object generated by the WriteXml() method.”

Lets dive into the code and implement the interface in our good old “Car” class. One thing to keep in mind before implementing IXmlSerializable interface is that the class which implements the interface should have a parameter less constructor. If the class which is implementing IXmlSerializable interface doesn’t have a parameter less constructor then on serialization the following error will be thrown.

Vehicles.Car cannot be serialized because it does not have a parameterless constructor.

Below is the Car class with IXmlSerializable interface implementation.

[System.Xml.Serialization.XmlRoot(Namespace="http://car/car")]
public class Car : System.Xml.Serialization.IXmlSerializable
{
    #region Properties
    public string Color
    { get; set; }

    public string ModelName
    { get; set; }

    public int Price
    { get; set; }

    public double CubicCentimeter
    { get; set; }
    #endregion

    #region IXmlSerializable Members

    System.Xml.Schema.XmlSchema System.Xml.Serialization.IXmlSerializable.GetSchema()
    {
        throw new NotImplementedException();
    }

    void System.Xml.Serialization.IXmlSerializable.ReadXml (System.Xml.XmlReader reader)
    {
        string ns = "http://car/car";
        if (reader.NodeType == System.Xml.XmlNodeType.Element && reader.Name == "Car")
        {
            reader.MoveToAttribute("Price");
            this.Price = reader.ReadContentAsInt();
            reader.MoveToElement();
        }

        reader.Read();

        if (reader.NodeType == System.Xml.XmlNodeType.Element && reader.Name == "ModelName")
        {
            this.ModelName = reader.ReadElementContentAsString();
        }

        if (reader.NodeType == System.Xml.XmlNodeType.Element && reader.Name == "CC")
        {
            this.CubicCentimeter = reader.ReadElementContentAsDouble() ;
        }

        if (reader.NodeType == System.Xml.XmlNodeType.Element && reader.Name == "Color")
        {
            this.Color= reader.ReadElementContentAsString();
        }       
    }

    void System.Xml.Serialization.IXmlSerializable.WriteXml (System.Xml.XmlWriter writer)
    {       
        string ns = "http://car/car";
        writer.WriteStartAttribute("Price");
        writer.WriteString(this.Price.ToString());
        writer.WriteEndAttribute();
        writer.WriteStartElement("ModelName", ns);
            writer.WriteString(this.ModelName);
        writer.WriteEndElement();
        writer.WriteStartElement("CC", ns);
            writer.WriteString(this.CubicCentimeter.ToString());
        writer.WriteEndElement();
        writer.WriteStartElement("Color", ns);
            writer.WriteString(this.Color);
        writer.WriteEndElement();   
    }

    #endregion
}

The above code is pretty straight forward, in the WriteXml method we are making use of the XmlWriter class and serializing the Car class in our customized way. We are writing the Price property as an attribute to the root element by making use of the “WriteStartAttribute” method of the XmlWriter class.  Similarly we are making use of “WriteStartElement” method of the XmlWriter class to write the other properties as an element node. The XML generated by WriteXml is pasted below.

<?xml version="1.0"?>
<Car Price="23423" xmlns="http://car/car">
  <ModelName>Beetle</ModelName>
  <CC>0</CC>
  <Color>Yellow</Color>
</Car>

Seeing the XML output you will be thinking that in “WriteXml” method we have not written code to write the “Car” root node, then how come there is a root node? The reason is that when the XmlWriter object, which is passed as an argument to the WriteXml method, already has the root node. By default the name of the root node will be the class name itself. If you notice we have placed the “XmlRoot” attribute on the “Car” class. So if you want to control the root name then you have to make use of the “XmlRoot” attribute.

In the ReadXml method we are making use of XmlReader class to read the Xml serialized using the WriteXml method and reconstructing our Car class. When the Car class is deserialized the XML serializer executes the ReadXml method by passing the XmlReader class as an argument. When ReadXml is called the reader is positioned at the start of the document. The ReadXml function is  making use of various methods of the XmlReader class to read the content of the XML and reconstructing our serialized Car class. We are checking whether the reader is currently pointing to a attribute or element and then making use of appropriate methods to read the content.

Since there is no validation happening during serializing and deserializing we have left the “GetSchema” method empty. If you really want to return a schema then what should you do?

Returning a schema from IXmlSerializable implemented class.

In scenarios where you want to return a schema file one should apply “XmlSchemaProvider” attribute to the IXmlSerializable implemented class. “XmlSchemaProvider” contains the name of the method which returns the schema for the class. The method which is specified in the “XmlSchemaProvider” should be static. Also the method should return an object of type “XmlQualifiedName” and should take “XmlSchemaSet” object as an argument. The method should take the responsibility of filling the “XmlSchemaSet” object passed as an argument. If you are applying “XmlSchemaProvider” to a class then make sure the schema files don’t have any schema errors else error will thrown during the serialization process.

Everything apart lets see how to implement “XmlSchemaProvider” attribute on a IXmlSerialization implemented class. Below is the Car class with the “XmlSchemaProvider” attribute applied. I have pasted only the relevant code.

[System.Xml.Serialization.XmlSchemaProvider("ReturnSchema")]
public class Car : System.Xml.Serialization.IXmlSerializable
{
    /*Code has been trimmed for brevity*/

    public static System.Xml.XmlQualifiedName ReturnSchema (System.Xml.Schema.XmlSchemaSet xs)
    {
        System.Xml.Serialization.XmlSerializer xmlSer = new System.Xml.Serialization.XmlSerializer(typeof (System.Xml.Schema.XmlSchema));
        System.Xml.Schema.XmlSchema xmlSchema = (System.Xml.Schema.XmlSchema) xmlSer.Deserialize (new System.Xml.XmlTextReader("CustomSerilize_app1.xsd"));
        xs.XmlResolver = new System.Xml.XmlUrlResolver();
        xs.Add(xmlSchema);
        return new System.Xml.XmlQualifiedName ("Car", “http://car/car”);
     }
}

I don’t think the above code needs much of an explanation. In the first few lines of code we are pumping the XmlSchema object with the schema information by reading the schema file from disk. Once that is done we are returning a XmlQualifiedName object.

That’ about customizing XML serialization. Isn’t it so easy to write custom XML serialization. Whenever you find a situation where you want to serialize your class into XML and you don’t want to leave it to the system and want to take full control of the serialization, you can implement IXmlSerializable interface to your class.

Try to know more

Sandeep

No comments:

Post a Comment

Please provide your valuable comments.