DekGenius.com
[ Team LiB ] Previous Section Next Section

9.3 XML Serialization

XML serialization addresses the requirements mentioned in the previous section. In XML serialization, no assumptions are made about the program that produces the XML or the one that reads it. You may lose some precise CLR type detail, but interoperation with disparate applications is better than with runtime serialization. In order to completely divorce the XML from the CLR, the serialization uses XML Schema datatypes. I mentioned in Table 8-2 that not every XSD datatype has a corresponding CLR datatype. Although each XSD datatype mapped to exactly one CLR datatype, many CLR datatypes could, potentially, each be represented by a number of different XSD datatypes.

The essential point to remember when differentiating runtime serialization from XML serialization is that, in the runtime serialization, the object being serialized actively controls the format of the serialization, whereas in the XML serialization, the object is passively serialized.

The XmlSerializer type contains the methods Serialize( ) and Deserialize( ). Any object can be serialized to XML and, by default, all fields in an object are serialized as elements. Certain attributes can also be used to decorate existing classes and methods. Table 9-2 shows a complete listing of attributes that affect XML serialization.

Table 9-2. Attributes that affect XML serialization

Attribute name

Description

XmlAnyAttributeAttribute

Place this attribute on a member whose type or return type is an array of XmlAttribute or XmlNode. Any attributes deserialized from XML that do not have a corresponding member in the class are placed in the array.

XmlAnyElementAttribute

Place this attribute on a member whose type or return type is an array of XmlElement or XmlNode. Any elements deserialized from XML that do not have a corresponding member in the class are placed in the array.

XmlArrayAttribute

Place this attribute on a member that returns an array of objects to produce nested XML elements.

XmlArrayItemAttribute

Place this attribute on a member that returns an array of objects to indicate the type of each of the nested XML elements.

XmlAttributeAttribute

Place this attribute on a member to indicate that it is to be serialized as an XML attribute.

XmlChoiceIdentifierAttribute

Place this attribute on a member to indicate that the type of the data to be serialized is indicated by another member, returning an enumeration.

XmlElementAttribute

Place this attribute on a member to indicate that it is to be serialized as an XML element.

XmlEnumAttribute

Place this attribute on a member of an enumeration to set the name that XmlSerializer uses for the member.

XmlIgnoreAttribute

Place this attribute on a member to indicate that it should be ignored for purposes of serialization.

XmlIncludeAttribute

Place this attribute on a member to have XmlSerializer recognize base and derived classes.

XmlRootAttribute

Place this attribute on a class to indicate that the class should be serialized as the document element.

XmlTextAttribute

Place this attribute on a member to indicate that it should be serialized as XML text.

XmlTypeAttribute

Place this attribute on a class to indicate the name of the type and namespace of the XML element.

System.ComponentModel.DefaultValueAttribute

Place this attribute on a member to indicate the default value for a member if no value is assigned.

With these attributes, you can take an arbitrary C# type and tell XmlSerializer exactly how you would like to serialize it to XML.

Example 9-7 shows a new XML format for an instance of the personnel records from Example 9-4.

Example 9-7. Angus Hardware personnel records in XML
<?xml version="1.0"?>
<personnel>
<employee firstname="Niel" middleinitial="M" lastname="Bornstein" 
    hiredate="2001-01-01T00:00:00.0000000-05:00">
    <addresses>
      <address type="Home">
        <street>999 Wilford Trace</street>
        <city>Atlanta</city>
        <state>Georgia</state>
        <zip>30037</zip>
      </address>
    </addresses>
  </employee>
</personnel>

If you were to just serialize a Personnel object to XML, all the data would appear in elements as shown in Example 9-8.

Example 9-8. XML serialized without attributes
<?xml version="1.0"?>
<Personnel xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/
2001/XMLSchema-instance">
  <Employees>
    <Employee>
      <FirstName>Niel</FirstName>
      <MiddleInitial>M</MiddleInitial>
      <LastName>Bornstein</LastName>
      <Addresses>
        <Address>
          <AddressType>Home</AddressType>
          <Street>
            <string>999 Wilford Trace</string>
          </Street>
          <City>Atlanta</City>
          <State>GA</State>
          <Zip>30037</Zip>
        </Address>
      </Addresses>
      <HireDate>2001-01-01T00:00:00.0000000-05:00</HireDate>
    </Employee>
  </Employees>
</Personnel>

That's fine, if you want all your data in elements, but some people prefer a healthy mix of elements and attributes; this element-centric output does not match the format in Example 9-7. In addition, the element names don't match the format you want.

To generate the XML you want, repeat the code from Example 9-4, with the addition of attributes to control the serialization. Let me step through the changes in each class. First, AddressType doesn't need to change at all:

public enum AddressType {
  Home,
  Office,
  Billing,
  Shipping,
  Mailing,
  Day,
  Evening,
  FAX
}

If you'll look again at Example 9-7, you'll see that each state is actually listed by its full name, not the abbreviation as listed in the State enumeration. Here I've added an XmlEnumAttribute for each state name. Note that I've skipped some in the interest of space:

public enum State {
  [XmlEnum(Name="Alaska")]
  AK,
  [XmlEnum(Name="Alabama")]
  AL,
  [XmlEnum(Name="Arkansas")]
  AR,
  [XmlEnum(Name="Arizona")]
  AZ,
// ...
  [XmlEnum(Name="Washington")]
  WA,
  [XmlEnum(Name="Wisconsin")]
  WI,
  [XmlEnum(Name="West Virginia")]
  WV,
  [XmlEnum(Name="Wyoming")]
  WY
}

The Address class has one attribute, type, and four elements. Here I've added XmlAttributeAttribute and XmlElementAttribute, as appropriate. The AttributeName and ElementName fields of each attribute are used to set the names of the XML attributes and elements, respectively:

public class Address {
  [XmlAttribute(AttributeName="type")]
  public AddressType AddressType;
  [XmlElement(ElementName="street")]
  public string[ ] Street;
  [XmlElement(ElementName="city")]
  public string City;
  [XmlElement(ElementName="state")]
  public State State;
  [XmlElement(ElementName="zip")]
  public string Zip;
}

Similar to Address, the TelephoneNumber class has one attribute and three elements. Again, I've decorated each member with the appropriate attribute. Note also that here, as in Address, I've set the names of the attributes and elements to match the ones in the XML; that is, they all start with lowercase letters:

public class TelephoneNumber {
  [XmlAttribute(AttributeName="type")]
  public AddressType AddressType;
  [XmlElement(ElementName="areacode")]
  public string AreaCode;
  [XmlElement(ElementName="exchange")]
  public string Exchange;
  [XmlElement(ElementName="number")]
  public string Number;
}

Now we come to the meat of the personnel record, the Employee. This class has three attributes: firstname, middleinitial, and lastname, which I've treated with the appropriate attribute. However, the Employee class also has two additional elements, addresses and telephones. These two elements actually contain nested arrays of elements, so I've used the XmlArray and XmlArrayItem attributes to help the serializer figure out what to do with the XML elements it reads:

public class Employee {
  [XmlAttribute(AttributeName="firstname")]
  public string FirstName;
  [XmlAttribute(AttributeName="middleinitial")]
  public string MiddleInitial;
  [XmlAttribute(AttributeName="lastname")]
  public string LastName;
  
  [XmlArray(ElementName="addresses")]
  [XmlArrayItem(ElementName="address")]
  public Address [ ] Addresses;
  [XmlArray(ElementName="telephones")]
  [XmlArrayItem(ElementName="telephone")]
  public TelephoneNumber [ ] TelephoneNumbers;

  [XmlAttribute(AttributeName="hiredate")]
  public DateTime HireDate; 
}

Here's the document element, personnel, which is decorated with XmlRootAttribute. Although the Employees member is an array of Employee objects, it is not a nested array, like addresses and telephones. By adding the XmlElement attribute directly to the member, the XmlSerializer knows that this member is to be serialized as an array of employee elements, without a separate top-level element:

[XmlRoot(ElementName="personnel")]
public class Personnel {
  [XmlElement(ElementName="employee")]
  public Employee [ ] Employees;
}

Finally, I've made some changes to the Serializer class, which I introduced in Example 9-5. Serializer's Main( ) method still uses the CreatePersonnel( ) to create some personnel records, but it then instantiates an XmlSerializer to deserialize the objects it created back out to a file:

public class Serializer {
  public static void Main(string [ ] args) {
    Personnel personnel = CreatePersonnel( );
    XmlSerializer serializer = new XmlSerializer(typeof(Personnel));
    using (FileStream stream = File.OpenWrite("Personnel.xml")) {
      serializer.Serialize(stream,personnel);
    }
  } 
}

Unlike the SoapFormatter and BinaryFormatter, the XmlSerializer constructor takes the Type of the object being serialized as a parameter. This is because, unlike the formatters, the serializer is actually created specifically to handle one particular type of object.

The XmlSerializer actually generates and compiles the source code of a class to serialize the object to XML at runtime. Because of this, you may notice a slight performance difference the first time you instantiate an XmlSerializer for a particular type during each run of your program.


Deserializing an object from XML is as simple as calling the XmlSerializer's Deserialize( ) method:

XmlSerializer serializer = new XmlSerializer(typeof(Personnel));
using (FileStream stream = File.OpenRead("Personnel.xml")) {
  personnel = (Personnel)serializer.Deserialize(stream);
}

This data is being serialized to and deserialized from files, but it could be any Stream. When deserializing from an XmlReader, you can ensure that the data stream is valid for the XmlSerializer instance you're using. The CanDeserialize( ) method takes an XmlReader parameter, and returns a Boolean value indicating whether the XmlReader contains data that can be deserialized by the XmlSerializer.

This is convenient, because when you're deserializing data from a source outside of your control, you don't always know what the file contains. The CanDeserialize( ) method can be used to control processing when you're unsure of the XML stream's contents.

At runtime, you can override the attributes that affect serialization with the XmlAttributeOverrides class. This class serves as the container for a collection of XmlAttributes instances, each one of which holds the overridden attributes for a particular type. XmlAttributes has a property for each type of XML attribute; for example, the XmlAttributeAttribute can be set with the XmlAttribute property. For those attributes that can exist in multiples, such as XmlElementAttribute, the property returns a collection of those attributes. For example, the XmlElements property returns a XmlElementAttributes collection, to which you can add XmlElementAttribute instances.

XmlAttributeOverrides is convenient if you want to serialize an object for which you don't have or can't alter the source code. You can customize the serialization in exactly the same ways as you could by applying the attributes in the source.

I've altered the same program we've been using to change the name of the root element from personnel to employees. The new lines are highlighted:

Personnel personnel = CreatePersonnel( );

XmlAttributeOverrides overrides = new XmlAttributeOverrides( );
XmlAttributes attributes = new XmlAttributes( );
attributes.XmlRoot = new XmlRootAttribute("employees");
overrides.Add(typeof(Personnel), attributes);

XmlSerializer serializer = 
  new XmlSerializer(typeof(Personnel), overrides);
using (FileStream stream = File.OpenWrite("Personnel.xml")) {
  serializer.Serialize(stream,personnel);
}
    [ Team LiB ] Previous Section Next Section