XML Serialization

19860 Users read it.
by George Petrov (August 30, 2002)

This article explains the XML Serialization process in detail.

It is part from the book XML and ASP.NET  - the one book that you need to learn about a wide range of XML technologies.

User Rating (78 votes)
0 reviews available
User Level:Intermediate
Language:C#

Using XSD Schemas for Strongly Typed XML Serialization

The uses for XML serialization that you've examined so far work best when you have control over the XML structures involved, but what if you needed to write a program to consume and/or produce XML that conforms to a particular schema? You could always use the classes in the System.Xml namespace to manually manipulate the XML, and rely on your own understanding of the XSD Schema in question to make sure that you produce valid XML. Perhaps you've realized that it is possible to create some classes that, through the use of XML serialization attributes, can be automatically serialized to an XML structure that conforms to the XSD Schema, allowing you to work with the XML data in a purely object-oriented fashion.

Creating serializable classes manually is certainly possible, but might become tedious when you work with a large or complicated schema. This again relies on your thorough understanding of the requirements of the XSD Schema in question to ensure that your classes never serialize to a nonconformant XML structure. Fortunately, Microsoft provided a better solution in the form of a command-line utility called xsd.exe, which is provided with the .NET Framework SDK. This utility has numerous uses that are beyond the scope of this chapter. What we're interested in is its ability to take an XSD Schema as input and produce the source code to a set of classes that can deserialize from any XML that conforms to the XSD Schema, and serialize into XML that can be understood by any other tool that expects the format specified in the schema. The generated source code can be in any language you specify, as long as you specify C#, Visual Basic .NET, or JScript .NET.

This ability has tremendous potential. It makes it almost trivial to exchange data with a business partner in an industry-standard XML format without ever having to touch the XML directly in your code. Here's an example: Let's go to the source of the XSD standard, the World Wide Web Consortium (W3C). We'll use the canonical Purchase Order Schema sample found in Section 2.1 at http://www.w3.org/TR/xmlschema-0/.

For those of you who aren't sitting in front of a computer as you read this, the Purchase Order Schema is reproduced in full in Listing 10.1.

Listing 10.1 The Purchase Order Schema

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

 <xsd:annotation>
 <xsd:documentation xml:lang="en">
  Purchase order schema for Example.com.
  Copyright 2000 Example.com. All rights reserved.
 </xsd:documentation>
 </xsd:annotation>

 <xsd:element name="purchaseOrder" type="PurchaseOrderType"/>

 <xsd:element name="comment" type="xsd:string"/>

 <xsd:complexType name="PurchaseOrderType">
 <xsd:sequence>
  <xsd:element name="shipTo" type="USAddress"/>
  <xsd:element name="billTo" type="USAddress"/>
  <xsd:element ref="comment" minOccurs="0"/>
  <xsd:element name="items" type="Items"/>
 </xsd:sequence>
 <xsd:attribute name="orderDate" type="xsd:date"/>
 </xsd:complexType>

 <xsd:complexType name="USAddress">
 <xsd:sequence>
  <xsd:element name="name"  type="xsd:string"/>
  <xsd:element name="street" type="xsd:string"/>
  <xsd:element name="city"  type="xsd:string"/>
  <xsd:element name="state" type="xsd:string"/>
  <xsd:element name="zip"  type="xsd:decimal"/>
 </xsd:sequence>
 <xsd:attribute name="country" type="xsd:NMTOKEN"
   fixed="US"/>
 </xsd:complexType>

 <xsd:complexType name="Items">
 <xsd:sequence>
  <xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
  <xsd:complexType>
   <xsd:sequence>
   <xsd:element name="productName" type="xsd:string"/>
   <xsd:element name="quantity">
    <xsd:simpleType>
    <xsd:restriction base="xsd:positiveInteger">
     <xsd:maxExclusive value="100"/>
    </xsd:restriction>
    </xsd:simpleType>
   </xsd:element>
   <xsd:element name="USPrice" type="xsd:decimal"/>
   <xsd:element ref="comment"  minOccurs="0"/>
   <xsd:element name="shipDate" type="xsd:date" minOccurs="0"/>
   </xsd:sequence>
   <xsd:attribute name="partNum" type="SKU" use="required"/>
  </xsd:complexType>
  </xsd:element>
 </xsd:sequence>
 </xsd:complexType>

 <!-- Stock Keeping Unit, a code for identifying products -->
 <xsd:simpleType name="SKU">
 <xsd:restriction base="xsd:string">
  <xsd:pattern value="\d{3}-[A-Z]{2}"/>
 </xsd:restriction>
 </xsd:simpleType>

</xsd:schema>

Generating Classes from an XSD Schema

Creating a set of classes from this schema is easy:

  1. Create a text file on your hard drive with the XSD Schema as the contents. (It's easiest to copy and paste from the W3C website.)

  2. Rename the text file to PurchaseOrder.xsd.

  3. Open a command prompt and navigate to the directory where you placed this file.

In order to use xsd.exe from the command prompt, the directory containing xsd.exe must be in your Path environment variable. If you have Visual Studio .NET installed, the Visual Studio .NET Command Prompt link located in the Visual Studio .NET Tools folder in your Start menu takes care of setting the proper environment variables for you. Enter the following command at the prompt:

xsd.exe PurchaseOrder.xsd /classes

This command generates a set of classes from the XSD file in the default language (C#). The generated classes are created in a file called PurchaseOrder.cs and looks something like what's shown in Listing 10.2.

Listing 10.2 C# Code Generated from PurchaseOrder.xsd

using System.Xml.Serialization;

[System.Xml.Serialization.XmlRootAttribute("purchaseOrder", Namespace="", 
IsNullable=false)] public class PurchaseOrderType { public USAddress shipTo; public USAddress billTo; public string comment; [System.Xml.Serialization.XmlArrayItemAttribute("item",
IsNullable=false)] public ItemsItem[] items; [System.Xml.Serialization.XmlAttributeAttribute(DataType="date")] public System.DateTime orderDate; [System.Xml.Serialization.XmlIgnoreAttribute()] public bool orderDateSpecified; } public class USAddress { public string name; public string street; public string city; public string state; public System.Decimal zip; [System.Xml.Serialization.XmlAttributeAttribute(DataType="NMTOKEN")] [System.ComponentModel.DefaultValueAttribute("US")] public string country = "US"; } public class ItemsItem { public string productName; [System.Xml.Serialization.XmlElementAttribute(DataType="positiveInteger")] public string quantity; public System.Decimal USPrice; public string comment; [System.Xml.Serialization.XmlElementAttribute(DataType="date")] public System.DateTime shipDate; [System.Xml.Serialization.XmlIgnoreAttribute()] public bool shipDateSpecified; [System.Xml.Serialization.XmlAttributeAttribute()] public string partNum; }

Using these classes, you can take any XML document that conforms to the Purchase Order Schema, such as the sample purchase order on the same page that the XSD Schema came from, and deserialize it into a PurchaseOrderType object by using the techniques introduced in the previous section. You can then manipulate the object and its properties just like you would with any other object, and serialize it to a new XML document that conforms to the original XSD Schema. You do this without ever touching the XML directly in your code. Let's do exactly that.

Manipulating XML Using Schema-Generated Classes

You are going to use the classes that xsd.exe just generated for you from PurchaseOrder.xsd to read and modify the sample purchase order listed on the same page of the W3C site that we got the XSD file from. This purchase order is reproduced in Listing 10.3.

Listing 10.3 The Purchase Order, po.xml

<?xml version="1.0"?>
<purchaseOrder orderDate="1999-10-20">
  <shipTo country="US">
    <name>Alice Smith</name>
    <street>123 Maple Street</street>
    <city>Mill Valley</city>
    <state>CA</state>
    <zip>90952</zip>
  </shipTo>
  <billTo country="US">
    <name>Robert Smith</name>
    <street>8 Oak Avenue</street>
    <city>Old Town</city>
    <state>PA</state>
    <zip>95819</zip>
  </billTo>
  <comment>Hurry, my lawn is going wild!</comment>
  <items>
    <item partNum="872-AA">
      <productName>Lawnmower</productName>
      <quantity>1</quantity>
      <USPrice>148.95</USPrice>
      <comment>Confirm this is electric</comment>
    </item>
    <item partNum="926-AA">
      <productName>Baby Monitor</productName>
      <quantity>1</quantity>
      <USPrice>39.98</USPrice>
      <shipDate>1999-05-21</shipDate>
    </item>
  </items>
</purchaseOrder>

Use the classes generated for you by xsd.exe to programmatically add a new item to the purchase order:

  1. Start by creating a new Console application called PurchaseOrder. Add a new XML file to the project called po.xml and enter the contents of Listing 10.3 into the file (or copy and paste the listing from the W3C website referenced earlier, if you can).

  2. Add an existing item to the project and browse to the location where you saved the PurchaseOrder.cs file that was generated earlier by xsd.exe. If you want, create a new class file and enter the code from Listing 10.2 instead.

  3. Open the Class1.cs file that was created for you with the project, and add the following using statements:

  4. using System.IO;
    using System.Xml;
    using System.Xml.Serialization;
  5. Inside the Main routine, enter the code to deserialize po.xml into a set of objects:

  6. // deserialize the existing purchase order
    XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrderType));
    FileStream fs = File.OpenRead(".\\po.xml");
    
    PurchaseOrderType order; 
    
    try
    {
       order = (PurchaseOrderType)serializer.Deserialize(fs);
    }
    finally
    {
       fs.Close();
    }

    Referring back to Listing 10.2, you can see that the first class listed is PurchaseOrderType. It has an XmlRoot attribute attached to it that tells you that this class will serialize to (and deserialize from) an XML root element called purchaseOrder. Because this class corresponds to the XML root element, it is the class that you pass to the XmlSerializer constructor. The rest of the deserialization code should look pretty familiar to you: You open the po.xml file, declare an instance of the PurchaseOrderType object, and fill it with the object returned from the XmlSerializer's Deserialize method.

  7. Create a new item to add to the purchase order. Referring back to Listing 10.2, you see that the generated class for holding items is called ItemsItem.

  8. ItemsItem newItem = new ItemsItem();
    newItem.partNum = "352-AA";
    newItem.productName = "Hedge Trimmer";
    newItem.quantity = "1";
    newItem.USPrice = 27.95m;

    The m suffix on the value you're passing to newItem.USPrice simply tells the C# compiler to treat a literal number as a decimal data type because that's the type defined for the USPrice field of the ItemsItem class.

  9. You need to add the newly created item to the list of items in the purchase order. Referring back to Listing 10.2, you see that the items are held in an array of type ItemsItem in the PurchaseOrderType class. What you must do is create a new ItemsItem array that's the same size as the original array, plus one. Then you must copy the current array into the new array and fill the empty slot with the newly created item.

  10. // create a new array of type ItemsItem to hold the current items plus the new one
    ItemsItem[] allItems = new ItemsItem[order.items.Length + 1];
    
    // copy the current items into the new array
    Array.Copy(order.items, allItems, order.items.Length);
    
    // add our new item to the array
    allItems[allItems.Length - 1] = newItem;
    
    // set the order's item array to our new array
    order.items = allItems;

    The last line in the previous code simply replaces the list of items in the purchase order with the new list.

  11. All you need to do is serialize the modified PurchaseOrderType object back to XML. Create a new file so the original file is not overwritten.

  12. // serialize the modified purchase order
    fs = File.Open(".\\po_new.xml", FileMode.OpenOrCreate);
    XmlTextWriter writer = new XmlTextWriter(fs, System.Text.Encoding.UTF8); 
    writer.Formatting = Formatting.Indented;
    
    try
    {
       serializer.Serialize(writer, order);
    }
    finally
    {
       // close the XmlTextWriter, which closes the underlying
    // stream writer.Close(); } Console.WriteLine("Purchase order modified!");
  13. You must do one more thing before you can compile the project and run it. The code you wrote expects to find po.xml in the current directory, but the directory that the code will be running from is actually the PurchaseOrder\bin\Debug subdirectory of the Project directory. Use Windows Explorer to copy po.xml into this directory. If the directory doesn't exist, you can click Build Solution in the Build menu to create it.

  14. After everything is ready, compile and run the project. You should find a file called po_new.xml in the same \bin\Debug directory into which you just copied po.xml. Open it; the contents should match what's shown in Listing 10.4.

Listing 10.4 The Modified Purchase Order

<?xml version="1.0" encoding="utf-8"?>
<purchaseOrder xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" orderDate="1999-10-20"> <shipTo> <name>Alice Smith</name> <street>123 Maple Street</street> <city>Mill Valley</city> <state>CA</state> <zip>90952</zip> </shipTo> <billTo> <name>Robert Smith</name> <street>8 Oak Avenue</street> <city>Old Town</city> <state>PA</state> <zip>95819</zip> </billTo> <comment>Hurry, my lawn is going wild!</comment> <items> <item partNum="872-AA"> <productName>Lawnmower</productName> <quantity>1</quantity> <USPrice>148.95</USPrice> <comment>Confirm this is electric</comment> </item> <item partNum="926-AA"> <productName>Baby Monitor</productName> <quantity>1</quantity> <USPrice>39.98</USPrice> <shipDate>1999-05-21</shipDate> </item> <item partNum="352-AA"> <productName>Hedge Trimmer</productName> <quantity>1</quantity> <USPrice>27.95</USPrice> </item> </items> </purchaseOrder>

As you can see, the new hedge trimmer has been added to the purchase order, but at no time did we manipulate any XML, or even the Document Object Model, directly.

Another feature of the xsd.exe utility bears mentioning—it works in reverse as well. You can pass it a compiled assembly and the name of a type located in that assembly, and xsd.exe generates an XSD Schema from that class that conforms to any public properties or fields of the class, and any XML serialization-related attributes that have been applied to the class. The utility is described in the .NET Framework documentation if you're interested in this functionality.

George Petrov

George PetrovGeorge Petrov is the Founder of the highly successful Zones: DMXzone.com, FWzone.net, FLzone.com, CFzone.net, DNzone.com and FlashFiles.nl

He has developed the comprehensive Dynamic Zones Content Management System that is the brains driving them and constantly supports their online communities with his extensive and valuable knowledge.

He is a renowned web development 'Guru' who has written many articles and extension for Macromedia's Dreamweaver/MX and UltraDev.

George Petrov is also the CEO and Founder of Dynamic Zones and also very proud of his children Sofie, Victor and Alexander!

See All Postings From George Petrov >>

Follow us on twitter Subscribe to our RSS feed
Activate your free membership today | Login | Currency