XML Serialization

Summary

 

Introduction

Serialization is the process of converting the state of an object into a form that can be persisted in a storage medium or transported across the processes/machines. The opposite of serialization is deserialization which is a process that converts the outcome of serialization into the original object. The .NET Framework offers two serialization technologies, binary and XML.

Binary Serialization

This is covered in the Binary Serialization chapter.

XML Serialization

XML Serialization serializes the public fields and properties of a class, or the parameters and return values of methods into an XML stream. XML Serialization does not include methods, indexers, private fields, or read-only properties (except read-only collections). Because XML is an open standard, the resulting XML stream can be processed by any application on any platform. For example, ASP.NET Web Services use XML Serialization to create XML streams to pass as data throughout the Internet or Intranets. Conversely, deserialization takes such streams and constructs an object. 

Unlike Binary Serialization, XML Serialization does not preserve type fidelity. In other words, it does not include type information. For example, if you a have an Employee object in the Company namespace, there is no guarantee that the object will be deserialized into the same type.

Use System.Xml.Serialization.XmlSerializer to perform XML serialization and deserialization. The most important methods are Serialize and Deserialize.

When creating classes, you have the option of either coding the class directly using C# or any .NET language,  or using the XML Schema Definition tool (xsd.exe) to generate classes based on an existing XSD. When an instance of a class generated from XSD is serialized, the generated XML adheres to the XML Schema. This is an alternative to using other classes in the .NET Framework such as XmlReader and XmlWriter. These classes allow you to parse any XML stream. In contrast, use XmlSerialzer when you want the XML stream to conform to a known XML Schema.

Attributes control the XML stream generated by XmlSerialzer allowing you to set the XML namespace, element name, attribute name, and so on in the generated XML stream. The XmlSerialzer class can further serialize an object and generate an encoded SOAP XML stream. Note that the XmlSerialzer class also generates the SOAP messages created by and passed to XML Web Services.

Serializable Items 

The following items can be serialized using XmlSerialzer:

Advantages of using XML Serialization

XmlSerialzer gives complete control over serializing an object into XML. For example, XmlSerialzer enables you to:

XML Serialization Considerations

The following should be considered when using XmlSerialzer class:

XSD Type Mapping

The data types contained in an XML Schema are defined by the World Wide Web consortium (www.w3.org). For many of these simple data types like int and decimal there is a corresponding data type in the .NET Framework. However, some XML data types do not have a corresponding data type in the .NET Framework (like the NMTOKEN data type). In this case, if you use xsd.exe to generate class definitions from an XML Schema, an appropriate attribute is applied to a member of type string and its DataType property is set to the XML data type name.

For example, if an XML Schema contains a data type named MyString with the XML data type NMTOKEN, the generated class will contain a member as follows:

[XmlElement( DataType="NMTOKEN" )]
public string MyString;

Similarly, if you are creating a class that must conform to a specific XML Schema, you should apply the appropriate attribute and set its DataType property to the desired XML data type name.

Example

The following example illustrates how to serialize a very simple and basic class. Note that the class must have a default constructor for XML sterilization to work:

// A simple serializable class
namespace XMLSerialization
{
    [Serializable()]
    public class MyBasicClass
    {
        public string strPublic;
        public MyBasicClass( )
        {
            strPublic = "hello";
        }
    }
}

// Serialization / Deserialization code
uisng System.Xml.Serialization;
...

private void btnXMLSerialization_Click(object sender, System.EventArgs e)
{
    XMLSerializeMyBasicCObject();
    XMLDeSerializeMyBasicCObject(); 

}

private void XMLSerializeMyBasicCObject()
{
    // Create a serializable instnace
    XMLSerialization.MyBasicClass ob = new XMLSerialization.MyBasicClass();

    // Initialize a storage medium to hold the serialized object
    Stream stream = new FileStream( "BasicXMLSerializa.xml", FileMode.Create, FileAccess.Write, FileShare.Write);

    // Serialize an object into the storage medium referenced by 'stream' object.
    XmlSerializer xmlserializer = new XmlSerializer( typeof(XMLSerialization.MyBasicClass) );
    xmlserializer.Serialize( stream, ob );

    // Cleanup
    stream.Close();
}

private void XMLDeSerializeMyBasicCObject()
{
    // Read the file back into a stream
    Stream stream = new FileStream( "BasicXMLSerializa.xml", FileMode.Open, FileAccess.Read, FileShare.Read);

    // Now create a binary formatter
    XmlSerializer xmlserializer = new XmlSerializer( typeof(XMLSerialization.MyBasicClass) );

    // Deserialize the object and use it
    XMLSerialization.MyBasicClass ob = (XMLSerialization.MyBasicClass)xmlserializer.Deserialize( stream );
    Trace.WriteLine( ob.strPublic );

    // Cleanup
    stream.Close();
}

Results of the serialization are shown below: 

<?xml version="1.0"?>
<MyBasicClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <strPublic>hello</strPublic>
</MyBasicClass>

Examples of XML Serialization

XML Serialization can be simple like the example above, or it can be complex. The following examples address various advanced scenarios.

DataSet Serialization 

The following shows how to serialize a DataSet:

private void btnDataSet_Click(object sender, System.EventArgs e)
{
    // Construct a DataSet
    System.Data.DataSet ds = new DataSet( "MyDataSet" );
    System.Data.DataTable dt = new DataTable( "MyDataTable" );
    System.Data.DataColumn dc1 = new DataColumn( "ID", typeof (int) );
    System.Data.DataColumn dc2 = new DataColumn( "Name", typeof (string) );

    dt.Columns.Add( dc1 );
    dt.Columns.Add( dc2 );
    ds.Tables.Add( dt );

    // Add some rows
    for (int i = 0; i < 5; i ++)
    {
        DataRow row = dt.NewRow();
        row[0] = i;
        row[1] = i.ToString();
        dt.Rows.Add ( row );
    }

    // Now serialize the DataSet
    System.Xml.Serialization.XmlSerializer serializer = new XmlSerializer( typeof( DataSet ) );
    Stream stream = new FileStream( "DataSet.xml", FileMode.CreateNew);
    serializer.Serialize( stream, ds );

    // Clean up
    stream.Close();
}

Contents of DataSet.xml :

<?xml version="1.0"?>
<DataSet>
 <xs:schema id="MyDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="MyDataSet" msdata:IsDataSet="true" msdata:Locale="en-GB">
        <xs:complexType>
            <xs:choice maxOccurs="unbounded">
                <xs:element name="MyDataTable">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="ID" type="xs:int" minOccurs="0" />
                            <xs:element name="Name" type="xs:string" minOccurs="0" />
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:choice>
        </xs:complexType>
    </xs:element>
 </xs:schema>
 <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
    <MyDataSet>
        <MyDataTable diffgr:id="MyDataTable1" msdata:rowOrder="0" diffgr:hasChanges="inserted">
            <ID>0</ID>
            <Name>0</Name>
        </MyDataTable>
        <MyDataTable diffgr:id="MyDataTable2" msdata:rowOrder="1" diffgr:hasChanges="inserted">
            <ID>1</ID>
            <Name>1</Name>
        </MyDataTable>
        <MyDataTable diffgr:id="MyDataTable3" msdata:rowOrder="2" diffgr:hasChanges="inserted">
            <ID>2</ID>
            <Name>2</Name>
        </MyDataTable>
        <MyDataTable diffgr:id="MyDataTable4" msdata:rowOrder="3" diffgr:hasChanges="inserted">
            <ID>3</ID>
            <Name>3</Name>
        </MyDataTable>
        <MyDataTable diffgr:id="MyDataTable5" msdata:rowOrder="4" diffgr:hasChanges="inserted">
            <ID>4</ID>
            <Name>4</Name>
        </MyDataTable>
    </MyDataSet>
 </diffgr:diffgram>
</DataSet>

Complex Object Serialization

If a property or a filed in a class returns a complex object (such as an array or a class instance), the XmlSerializer will convert it to an element nested within the main XML document. The following example illustrates. Pay special attention to items in bold:

// Classes used in serializing. An Order object contains 0 or more Item objects
namespace XMLSerialization
{
    [Serializable]
    public class Item
    {
        public string strProductName;
        public int nProductID;

        public Item()
        {
            strProductName = "";
            nProductID = 0; 
        }
        public Item( string s, int n )
        {
            strProductName = s;
            nProductID = n;
        }
    }

    [Serializable]
    public class Order
    {
        // Must indicate what kind of elements exist in the ArrayList. See
        // Controlling XML Serialization Through Attributes
        [XmlElement( typeof(Item) )]
        public ArrayList alOrders;

        public Order()
        {
            alOrders = new ArrayList();
        }

        [XmlInclude( typeof( Item) )]
        public void Add( Item i )
        {
            alOrders.Add( i );
        }
    }
}

// The function creates an Order that contains many Items and then serializes the Order object
private void btnComplexOb_Click(object sender, System.EventArgs e)
{
    Stream stream = null;
    try
    {
        // Create an object that contains other objects
        XMLSerialization.Order order = new XMLSerialization.Order();
        order.Add( new XMLSerialization.Item( "Apples", 10 ) );
        order.Add( new XMLSerialization.Item( "Oranges", 20 ) );
        order.Add( new XMLSerialization.Item( "Avocados", 30 ) );

        // Now serialize the class
        System.Xml.Serialization.XmlSerializer serializer = new XmlSerializer( typeof( XMLSerialization.Order ) );
        stream = new FileStream( "ComplexObject.xml", FileMode.Create );
        serializer.Serialize( stream, order );
        stream.Close();
    }
    catch( Exception ex )
    {
        stream.Close();
        Trace.WriteLine ( ex.Message );
    }
}

<?xml version="1.0"?>
<Order xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <alOrders>
        <strProductName>Apples</strProductName>
        <nProductID>10</nProductID>
    </alOrders>
    <alOrders>
        <strProductName>Oranges</strProductName>
        <nProductID>20</nProductID>
    </alOrders>
    <alOrders>
        <strProductName>Avocados</strProductName>
        <nProductID>30</nProductID>
    </alOrders>
</Order>

Serializing a class that implements ICollection

You write your own collection classes by implementing ICollection. With respect to XML serialization, it should be noted that when a class implements the ICollection interface, only the collection contained by the class is serialized. Any public fields or properties added to the class will not be serialized. The class must include an Add method and an indexer to be serialized.

XML Schema Definition Tool

The XML Schema definition tool xsd.exe is installed as part of the .NET Framework. The tool serves two main purposes:

xsd.exe MySchema.xsd

xsd.exe MyFile.dll

Controlling XML Serialization Through Attributes

Special XNL Attributes can be used either to:

For example, by default, an XML element name is determined by the class or member name. For example, class Customer with a public field called Name will produce an XML element tag called Name

public Customer
{
    public string Name;
    ...
}

<!-- When an instance of the above class is serialized, it might produce this stream -->
<Name>John</Name> 

This default behavior might not be appropriate if the element name is invalid as a member name. XmlElementAttribute allows you to change the name in the serialization stream:

public class Customer
{
    [XmlElement(ElementName = "John Doe"))
    public string strName
}

The following sections show how different XML attributes can be used to further control the serialization process.

Serializing Arrays

Serialization of array is controlled with XmlArrayAttribute and XmlArrayItemAttribute attributes. Using these attributes you can control the element name, namespace, and the XSD data type. XmlArrayAttribute will determine the properties of the enclosing element that results when the array is serialized, while  XmlArrayItemAttribute will determine the properties of the enclosed elements that result when the items contained in the array are serialized. Consider the following code and its XML serialization

public class Company
{
    public Employee[] Employees;
}
public class Employee
{
    public string name;
}

<Group>
    <Employees>
        <Employee>
            <name>Joe</name>
            <name>Doe</name>
            ...
        </Employee>
    </Employees>
</Group>

Now consider the effect of XmlArrayAttribute and XmlArrayItemAttribute attributes:

public class Company
{
    [XmlArray("SoftwareDevelopers")]
    [XmlArrayItem("Developer")]
    public Employee[] Employees;
}
public class Employee
{
    public string name;
}

<Group>
    <SoftwareDevelopers>
        <Developer>Joe</Developer>
        <Developer>Doe</Developer>
         ...
    </SoftwareDevelopers>
</Group>

Serializing Derived Classes

Another use of XmlArrayItemAttribute is to allow the serialization of derived classes. In the code just given above, assume that Manager is a class that derives from Employee and the the Employees array can contain objects of type Employee or type Manager. If the Employees array did contain objects of type Employee or type Manager, then serialization will fail at runtime because XmlSerializer does not know what to do when it encounters an object of type Manager in the Employees array. XmlArrayItemAttribute comes to the rescue at it allows to specify the type of the contained elements:

public class Company
{
    [XmlArrayItem(typeof(Employee)]
    [XmlArrayItem(typeof(Manager)]
    public Employee[] Employees;
}

public class Employee
{
    public string name;
}

public class Manager :Employee
{
    public string DepartmentName;
}

Serializing an Array as a sequence of Elements

You can serialize an array as a flat sequence of elements by applying the XmlElementAttribute attribute to the field returning the array:

public class Company
{
    [XmlElement]
    public Employee[] Employees;
}
public class Employee
{
    public string name;
}

<Group>
    <Employees>
        <Name>Joe</Name>
        <Name>Doe</Name>
         ...
    </Employees>
</Group>

Serializing an ArrayList

An ArrayList can contain objects of different types. As with arrays, you must inform XmlSerializer what types of objects are contained in the ArrayList. This can be done using XmlElementAttribute attribute just like XmlArrayItemAttribute attribute was used.

public class Company
{
    [XmlElement(typeof(Employee)]
    public ArrayList alEmployees
}

Serializing Classes

XmlRootAttribute and XmlTypeAttribute attributes can only be applies to classes. XmlRootAttribute attribute can only be applied to a single class which will represent the XML document's root element. XmlTypeAttribute attribute can be applied to any class including the root class.

[XmlRoot("NewRootName")]
[XmlType("NewTypeName")]
public class Group
{
    ...
}

Preventing Serialization

XmlIgnoreAttribute attribute should be applied to public fields and properties that should not be serialized.

Overriding XML Serialization

You can generate more than one XML stream when using XmlSerializer to serialize the same class. Typically this is done because two consumers of your XML stream (say two Web Services) require the same basic information but in two different formats.

Generating different XML streams for the same class is achieved using this XmlSerializer constructor:

public XmlSerializer( Type type, XmlAttributeOverrides overrides );

XmlAttributeOverrides is a type that allows you to override property, field, and class attributes during object serialization/deserialization. XmlAttributeOverrides contains a collection of the object types that will be overridden as well as an XmlAttributes object associated with each overridden type. The process for using XmlAttributeOverrides object is as follows:

  1. Create an XmlAttributes object for each overridden field, property or type.
  2. Create an attribute object that is suitable for the object being overridden.
  3. Add the attribute object created in step 2 to the appropriate XmlAttributes property or collection.
  4. Create an XmlAttributeOverrides object.
  5. Use the Add method on XmlAttributeOverrides to the add the XmlAttributes object created in step 1 and modified in step 3.
  6. Pass the XmlAttributeOverrides object to  the appropriate XmlSerializar constructor.
  7. Serialize/deserialize the object.

See example in MSDN

You can also create an alternate XML stream by deriving from existing classes and instructing XmlSerializer to serialize those classes.

Working with XML Namespaces

XML namespaces provide a method for qualifying the names of XML elements and XML attributes in XML documents to ensure that those names are unique in time and place. Recall that a qualified XML name consists of a prefix and a local name separated by a colon - XYZ:ABC. The prefix XYZ serves only as a placeholder, i.e., an alias,  as it is mapped to a URI that specifies a namespace.

XML namespaces are contained by instances of XmlSerializerNamespaces class. To create fully qualified names in an XML document:

  1. Create an instance of XmlSerializerNamespaces class and add the required (prefix, namespace) pairs.
  2. Apply the appropriate XML serialization attributes and at the same time setting the Namespace property of each such attribute to one of the namespaces specified in step 1.
  3. Pass the XmlSerializerNamespaces  instance to XmlSerializer.Serialize() method.

The following example illustrates:

namespace XMLNamespaceSerialization
{
    public class Price
    {
        // Serialize the currency field as an attribute with the given namspace
        [XmlAttribute( Namespace ="www.diranieh.com")]
        public string currency;

        // Serialize the price field as an attribute with the given namspace
        [XmlAttribute( Namespace ="www.diranieh.com")]
        public decimal price;
    }

    public class Book
    {
        // Serialize the title field as an element with the given namspace
        [XmlElement( Namespace = "www.diranieh.com") ]
        public string Title;

        // Serialize the price field as an element with the given namspace
        [XmlElement( Namespace = "www.diranieh.com") ]
        public Price price;
    }

    public class Books
    {
        // Serilize the alBooks filed with the given namespace
        [XmlElement(typeof(Book), Namespace = "www.diranieh.com")]
        public ArrayList alBooks = new ArrayList();
    }
}


private void btnNamespaces_Click(object sender, System.EventArgs e)
{
    /* Create a collection of books */

    // Book1
    XMLNamespaceSerialization.Book  book1  = new XMLNamespaceSerialization.Book();
    XMLNamespaceSerialization.Price price1 = new XMLNamespaceSerialization.Price();
    price1.currency = "USD";
    price1.price    = 49.99M;             // M suffix for decimal literals
    book1.Title     = "Advanced .NET";
    book1.price     = price1;

    // Book2
    XMLNamespaceSerialization.Book  book2  = new XMLNamespaceSerialization.Book();
    XMLNamespaceSerialization.Price price2 = new XMLNamespaceSerialization.Price();
    price2.currency = "GBP";
    price2.price    = 39.99M;             // M suffix for decimal literals
    book2.Title     = "Advanced C#";
    book2.price     = price2;

    // Add books to collection
    XMLNamespaceSerialization.Books books = new XMLNamespaceSerialization.Books();
    books.alBooks.Add( book1 );
    books.alBooks.Add( book2 );

    /* Create XML namespace pairs */
    XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
    namespaces.Add( "YD", "www.diranieh.com" );

    /* Serialize to file */
    Stream stream = new FileStream( "Namespaces.xml", FileMode.Create, FileAccess.Write, FileShare.Write );
    XmlSerializer serializer = new XmlSerializer( typeof( XMLNamespaceSerialization.Books ) );
    serializer.Serialize( stream, books, namespaces );
    stream.Close();
}

Output from the previous code is shown below:

<?xml version="1.0"?>
<Books xmlns:YD="www.diranieh.com">
    <YD:alBooks>
        <YD:Title>Advanced .NET</YD:Title>
        <YD:price currency="USD" price="49.99" />
    </YD:alBooks>
    <YD:alBooks>
        <YD:Title>Advanced C#</YD:Title>
        <YD:price currency="GBP" price="39.99" />
    </YD:alBooks>
</Books>

XML Serialization with XML Web Services

XML Serialization is the underlying transport mechanism used in XML Web Services. This serialization is performed by the XmlSerializer class. To control the XML generated by XML Web Services, you can apply different XML attributes (just like in previous examples) to classes, return values, parameters, and fields of code files (.asmx) used in XML Web Services.

The XML generated by an XML Web Service can be formatted in one of two ways: literal or encoded. Therefore, there two sets of attributes that control XML Serialization as follows:

Attributes that encode SOAP Serialization:

Attributes that encode XML Serialization:

By selectively applying these attributes, the application can be tailored to return either or both styles.