Customizing XML Serialization



Customizing XML Serialization

As you may have noticed in earlier examples, the use of attributes on your class and its members can affect the XML into which it serializes (and the XML that will serialize into it). Certain default rules apply when a class is serialized. These rules, covered in the following sections, are in addition to the rules mentioned previously regarding what will be serialized and how to make sure a class can be serialized in XML.

The Namespace of Serialized XML Will Be http://tempuri.org by Default

For example, the following class will be serialized with this "temporary" namespace:

public class Address 
{
     public String Street;
     public String City;
     public String State;
     public String ZipCode;
}

And the resulting XML is as follows:

<Address 
     xmlns="http://tempuri.org/">
    <Street>1 Microsoft Way</Street>
    <City>Redmond</City>
    <State>WA</State>
    <ZipCode>98052</ZipCode>
</Address>

To set the namespace of a class and the default namespace of its child elements, use the [XmlRoot] attribute. You can do this with the Namespace property of this attribute, as shown in the following code:

[XmlRoot(Namespace="http://address")] 
public class Address
{
     public String Street;
     public String City;
     public String State;
     public String ZipCode;
}

The resulting XML looks like this:

<Address xmlns="http://address"> 
      <Street>1 Microsoft Way</Street>
      <City>Redmond</City>
      <State>WA</State>
      <ZipCode>98052</ZipCode>
 </Address>

You can also use the [XmlRoot] attribute to change the name of the root element, as in the following example. Although you also can use it to set the data type of the element when schemas are used, this is of less value than the element name.

[XmlRoot("CustomerAddress", Namespace="http://address")] 
public class Address
{
     public String Street;
     public String City;
     public String State;
     public String ZipCode;
}

The preceding code serializes into this XML:

<CustomerAddress xmlns="http://address"> 
      <Street>1 Microsoft Way</Street>
      <City>Redmond</City>
      <State>WA</State>
      <ZipCode>98052</ZipCode>
</CustomerAddress>

Properties and Fields Will Remain in the Same Namespace

Often you will want child elements of XML to be in the same namespace as their parent XML elements, and this makes sense for a lot of XML documents. However, sometimes child elements will be in other namespaces. For example, it is possible to have namespaces with the same name but different namespaces (and thus different meaning). You can do this with the Namespace property of the [XmlElement] attribute, as shown in the following example:

public class Address 
{
     public String Street;
     public String City;
     public String State;
     public String ZipCode;
}

[XmlRoot("Customer", Namespace="http://customer")]
public class Customer
{
     public String Name;

     [XmlElement(Namespace="http://address")]
     public Address Address;
}

The resulting XML looks like this:

<Customer xmlns="http://customer"> 
      <Name>Keith Ballinger</Name>
      <Address xmlns="http://address">
        <Street>1 Microsoft Way</Street>
        <City>Redmond</City>
        <State>WA</State>
        <ZipCode>98052</ZipCode>
      </Address>
</Customer>

As you see, you also can:

  • Set names for the serialized XML that differ from the default, which is the name of the class member.

  • Use this attribute to set the data type, as you can with the [XmlRoot] attribute.

  • Set the element form to qualified or unqualified, using the Form property.

Properties and Fields Will Be Serialized as Elements

As has probably become evident, members of a class are serialized as child elements, by default. This is easily changed. Yet again, metadata comes to the rescue. The [XmlAttribute], when applied to field or property, serializes that property as an XML attribute, rather than as a child element.

NOTE

In C# metadata classes are called attributes. Of course, attributes also exist in XMLs. This can be confusing, so I'll try always to say XML attribute when I mean the XML kind.

Listing 5.3 shows an example that applies the [XmlAttribute].

Using the [XmlAttribute] Attribute
public class Address
{
     public String Street;
     public String City;
     public String State;
     [XmlAttribute]
     public String ZipCode;
}
[XmlRoot("Customer", Namespace="http://customer")]
public class Customer
{
     [XmlAttribute]
     public String Name;

     [XmlElement(Namespace="http://address")]
     public Address Address;
}

The resulting XML looks like this:

<Customer Name="string" xmlns="http://customer"> 
    <Address ZipCode="98052" xmlns="http://address">
        <Street>1 Microsoft Way</Street>
        <City>Redmond</City>
        <State>WA</State>
    </Address>
</Customer>

As with many of the other attributes, you also can set the namespace of any XML attributes.

Arrays with XML Serialization

When you serialize an array that is a member of a class, the result is a piece of structured XML that contains (a) a top element which is, by default, the name of the class member, and (b) any number of child elements, one for each value in the array. Each member is named the data type of the class member, such as "string". Listing 5.4 shows a class with arrays that will be serialized according to these rules.

A Class with an Array of Strings for a Property
public class Address
{
     public String[] Street;
     public String City;
     public String State;

     [XmlAttribute]
     public String ZipCode;
}
[XmlRoot("Customer", Namespace="http://customer")]
public class Customer
{
     [XmlAttribute]
     public String Name;

     [XmlElement(Namespace="http://address")]
     public Address[] Address;
}

The resulting XML looks like this:

<Customer Name="string" xmlns="http://customer"> 
  <Address ZipCode="98045" xmlns="http://address">
    <Street>
      <string>1 Microsoft Way</string>
      <string>Suite 1000</string>
    </Street>
    <City>Redmond</City>
    <State>WA</State>
  </Address>
  <Address ZipCode="98052" xmlns="http://address">
    <Street>
      <string>100 Main St.</string>
      <string>Suite 1</string>
    </Street>
    <City>Redmond</City>
    <State>WA</State>
  </Address>
</Customer>

To control the name of the single top element, you can use the ElementName property of the [XmlArray] attribute. Using the Namespace property, you also can set the namespace of this element. Listing 5.5 shows an example.

Using the [XmlArray] Attribute
public class Address
{
     [XmlArray("StreetName")]
     public String[] Street;

     public String City;
     public String State;
     [XmlAttribute]
     public String ZipCode;
}

[XmlRoot("Customer", Namespace="http://customer")]
public class Customer
{
     [XmlAttribute]
     public String Name;
     [XmlElement(Namespace="http://address")]
     public Address[] Address;
}

The resulting XML is as follows:

<Customer Name="string" xmlns="http://customer"> 
      <Address ZipCode="98045" xmlns="http://address">
        <StreetName>
          <string>1 Microsoft Way</string>
          <string>Suite 1000</string>
        </StreetName>
        <City>Redmond</City>
        <State>WA</State>
      </Address>
      <Address ZipCode="98052" xmlns="http://address">
        <StreetName>
          <string>100 Main St.</string>
          <string>Suite 1</string>
        </StreetName>
        <City>Redmond</City>
        <State>WA</State>
      </Address>
</Customer>

Even better, you can set the repeating child element to something more useful than the data type name. This is done with [XmlArrayItem] attri bute, as shown in Listing 5.6. This listing also shows how you can set the namespace and nesting level with this attribute.

Using the [XmlArrayItem] Attribute
public class Address
{
     [XmlArray("StreetName")]
     [XmlArrayItem("Name")]
     public String[] Street;
     public String City;
     public String State;

     [XmlAttribute]
     public String ZipCode;
}
[XmlRoot("Customer", Namespace="http://customer")]
public class Customer
{
     [XmlAttribute]
     public String Name;

     [XmlElement(Namespace="http://address")]
     public Address[] Address;
}

Here is the resulting XML:

<Customer Name="string" xmlns="http://customer"> 
      <Address ZipCode="98045" xmlns="http://address">
        <StreetName>
          <string>1 Microsoft Way</string>
          <string>Suite 1000</string>
        </StreetName>
        <City>Redmond</City>
        <State>WA</State>
      </Address>
      <Address ZipCode="98052" xmlns="http://address">
        <StreetName>
          <string>100 Main St.</string>
          <string>Suite 1</string>
        </StreetName>
        <City>Redmond</City>
        <State>WA</State>
      </Address>
</Customer>

With the [XmlElement] attribute , you can eliminate the wrapping element structure used in the serialization of arrays and have repeating elements only. An example of this is when the [XmlElement] attribute is applied to the Address inside the Customer class.

Serializing Untyped XML

Sometimes, you won't know the shape or type of the XML for a particular piece of your schema. The schema specification understands this and lets schemas indicate those parts as well. With XML Serialization, you can capture the XML in those elements and attributes just as easily. Of course, manipulating untyped XML is more difficult than manipulating plain old classes, but there is no other way.

Imagine your XML document looked something like this:

<Order> 
     <OrderDescription>
          This is an order for a thousand widgets.
     </OrderDescription>
     <Quantity>4</Quantity>
     <Name>Widget</Name>
</Order>

Normally, you could handle something like this with a normal class:

public class Order 
{
     public String OrderDescription;
     public int Quantity;
     public String Name;
}

But imagine if the <OrderDescription> element could contain whatever kind of XML the document creator wanted to put into it, not just the XML that is currently there. In such cases, you would need to say that the element type is a System.Xml.XmlNode, or something that derives from it—like System.Xml.XmlElement, as in the following example:

public class Order 
{
     public System.Xml.XmlElement OrderDescription;
     public int Quantity;
     public String Name;
}

Furthermore, imagine a scenario in which not all of the attributes on the <Order> element are known at design time. In such a scenario, you could create a field or property of type System.Xml.XmlElement and put the [XmlAnyAttribute] attribute on that field or property:

public class Order 
{
     [XmlAnyAttribute]
     public System.Xml.XmlAttribute[] anyAttributes;

     public System.Xml.XmlElement OrderDescription;
     public int Quantity;
     public String Name;
}

Now, any attributes would be added to this field.

Finally, you have the option of using the [XmlAnyElement] attribute to get any extra XML that you don't have a clue about:

public class Order 
{
     [XmlAnyElement]
     public System.Xml.XmlElement[] extraElements;

     public System.Xml.XmlElement OrderDescription;

     public int Quantity;
     public String Name;
}