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.
This is covered in the Binary Serialization chapter.
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.
The following items can be serialized using XmlSerialzer:
XmlSerialzer gives complete control over serializing an object into XML. For example, XmlSerialzer enables you to:
The following should be considered when using XmlSerialzer class:
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.
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>
XML Serialization can be simple like the example above, or it can be complex. The following examples address various advanced scenarios.
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>
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>
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.
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
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.
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>
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;
}
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>
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
}
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
{
...
}
XmlIgnoreAttribute attribute should be applied to public fields and properties that should not be serialized.
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:
See example in MSDN
You can also create an alternate XML stream by deriving from existing classes and instructing XmlSerializer to serialize those classes.
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:
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 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.