XML is hailed as the key to system interoperability, and the .NET framework provides a variety of classes for processing XML data. XmlDocument enables you to process XML data as a document, while XmlReader, XmlWriter, and their derived classes enable you to process XML data as a stream. XmlSerializer provides a third option, allowing you to serialize and deserialize your own objects as XML. Serialization combines the random-access convenience of document-style processing with the ability to skip elements that aren’t of interest. In this article, I’ll show you how to use XmlSerializer and how to decorate your classes with attributes to control the serialization process.
XmlSerializer, in System.Xml.dll under the System.Xml.Serialization namespace, provides XML serialization services in a highly decoupled manner. Your classes aren’t required to derive from a particular base class, nor are they required to implement any particular interfaces. Instead, you attach custom attributes to your classes and to their public fields and public read/write properties. XmlSerializer reads these attributes through reflection and uses them to map classes and their members to XML elements and attributes. For a discussion of .NET attributes, see "Upon 'reflection,' C# attributes can simplify programming tasks."
Mapping XML to objects
Consider the XML in Listing A, which describes the movies showing at a theater.
A Theater class with attribute mappings for XmlSerialier can be defined as shown in Listing B.
The XmlRoot attribute maps the class Theater to the XML root element theater. The XmlElement attributes map the Name, Phone, and Movies fields to name, phone, and movie XML elements nested within a theater element. Because Movies is an array of Movie, XmlSerializer maps it to multiple XML movie elements.
Listing C shows a Movie class with attribute mappings.
XmlElement attributes map the Title and Showings fields to the title and showing XML elements within a movie element. As with Theater.Movie, the Movie.Showings array of DateTime is mapped to multiple XML showing elements. The attribute for the showing field contains the positional attribute parameter DataType=“time”. This maps the DateTime value to an XML time value, discarding date information and keeping only the time information. The XmlAttribute attribute maps the Minutes field to an XML attribute rather than to an XML element.
The moviestars attribute and the rating element in the XML data aren’t mapped to anything in the Movie class. When deserializing XML data, XmlSerializer simply skips items it can’t map. When serializing an object, you can attach the XmlIgnore attribute to public fields and properties you want XmlSerializer to skip.
The attribute class names for XmlRoot, XmlElement, and XmlAttribute all include the suffix “Attribute.” I’ve used the shorthand form without the suffix in my attribute declarations. The public fields in the Theater and Movie classes could be changed to public properties for better encapsulation. XmlSerializer could use them just the same. I’ve made them fields here to keep the code more compact.
Deserializing XML into objects
Getting XML data into a Theater object is now very easy. The program in Listing D, XmlIn, creates a Theater object by deserializing the movie showings XML data. The program is executed from the command line, specifying an input XML file.
The key statements are located inside the try block in Main. An XmlSerializer is created specifying a System.Type object to tell the serializer the type of object to create. The typeof operator returns a System.Type object for the Theater class. Next, a file stream is opened to read the input XML file. The Deserialize method of XmlSerializer is called, passing the file stream. Deserialize returns a Theater object reference. The ToString methods in the Theater and Movie objects enable simple output.
Serializing objects into XML
Generating XML data from a Theater object model is also easy. The program in Listing E, XmlOut, serializes a Theater object to an XML file. The program is executed from the command line, specifying the output XML file.
The key statements are again located inside the try block in Main. A Theater object is created by the GetTheater helper method. An XmlSerializer is created as before. Next, a file stream is opened to create the output XML file. The Serialize method of XmlSerializer is called, passing the file stream and the Theater object. That’s it—XML file created!
The output theater element contains XML namespaces attributes (xmlns) for the schema and schema-instance namespaces, though the data doesn’t refer to anything in either namespace. The –06:00 in the showing elements indicates U.S. Central Time, or GMT minus six hours, my time zone.
Moving data's a breeze
XmlSerializer makes moving data between objects and XML very easy, given classes instrumented with XML mapping attributes. But for a more complicated object model, creating the XML mappings manually can become tedious and error prone. In my next article, I’ll show you a way to automate this task and exercise tighter control over your XML data.