Shaping the XML Output

XML serialization enables you to shape the final form of the XML data being created. Although the code of the class is not directly involved in the generation of the output, the programmer is given a couple of tools to significantly influence the serialization process.

The first approach is fairly static and works by setting attributes on the various members of the class to be serialized. According to the attribute set, a given member can be rendered as an attribute, an element, or plain text, or it can be ignored altogether. The second approach is more dynamic and, more importantly, does not require the availability of the class source code. This approach is particularly effective for achieving a rather odd yet realistic result: shaping an XML flow you can’t control to fit into a data structure you can’t modify.

XML Serialization Attributes

The XmlAttributes class represents a collection of .NET Framework attributes that let you exercise strict control over how the XmlSerializer class processes an object. The XmlAttributes class is similar to the SoapAttributes class mentioned in the section “Adding Type Information,” on page 494. Both classes perform the same logical operation, but the former outputs to XML, whereas the latter returns SOAP-encoded messages with type information.

Each property of the XmlAttributes class corresponds to an attribute class. The available XmlAttributes properties and their corresponding attribute classes are listed here:

  • XmlAnyAttribute

    Corresponds to the XmlAnyAttributeAttribute attribute and applies to properties that return an array of XmlAttribute objects. A property marked with this attribute is populated with any unknown attribute detected during the deserialization process.

  • XmlAnyElements

    Corresponds to the XmlAnyElementAttribute attribute and applies to properties that return an array of XmlElement objects. A property marked with this attribute contains all the unknown elements found.

  • XmlArray

    Corresponds to the XmlArrayAttribute attribute and applies to all properties that return an array of user-defined objects. This attribute causes the contents of the property to be rendered as an XML array. An XML array is a subtree in which child elements are recursively serialized and appended to a common parent node.

  • XmlArrayItems

    Corresponds to the XmlArrayItemAttribute attribute and applies to all properties that return an array of objects. Tightly coupled with the previous attribute, XmlArrayItemAttribute describes the type of the items in the array. XmlArrayItemAttribute specifies how the serializer renders items inserted into an array.

  • XmlAttribute

    Corresponds to the XmlAttributeAttribute attribute and applies to public properties, causing the serializer to render them as attributes. By default, if no attribute is applied to a public read/write property, it will be serialized as an XML element.

  • XmlChoiceIdentifier

    Corresponds to the XmlChoiceIdentifier­Attribute attribute and implements the xsi:choice XSD data structure. The xsi:choice data type resembles the C++ union structure and consists of additional properties, only one of which is valid for each instance. The XmlChoiceIdentifierAttribute attribute lets you express the choice of which data member to consider for serialization.

  • XmlDefaultValue

    Corresponds to the XmlDefaultValueAttribute attribute and gets or sets the default value of an XML element or attribute.

  • XmlElement

    Corresponds to the XmlElementAttribute attribute and forces the serializer to render a given public field as an XML element.

  • XmlEnum

    Corresponds to the XmlEnumAttribute attribute and specifies the way in which an enumeration member is serialized. You use this attribute class to change the enumeration that the XmlSerializer generates and recognizes when deserializing.

  • XmlIgnore

    Corresponds to the XmlIgnoreAttribute attribute and specifies whether a given property should be ignored and skipped or serialized to XML as the type dictates. The attribute requires no further properties to be specified.

  • XmlRoot

    Corresponds to the XmlRootAttribute attribute and overrides any current settings for the root node of the XML serialization output, replacing it with the specified element.

  • XmlText

    Corresponds to the XmlTextAttribute attribute and instructs the XmlSerializer class to serialize a public property as XML text. The property to which this attribute is applied must return primitive and enumeration types, including an array of strings or objects. If the return type is an array of objects, the Type property of the XmlTextAttribute type must be set to string, and the objects will then be serialized as strings. Only one instance of the attribute can be applied in a class.

  • XmlType

    Corresponds to the XmlTypeAttribute attribute and can be used to control how a type is serialized. When a type is serialized, the XmlSerializer class uses the class name as the XML element name. The TypeName property of the XmlTypeAttribute class lets you change the XML element name. The IncludeInSchema property lets you specify whether the type should be included in the schema.

The XmlElement Attribute

The key XML attributes are XmlElement and XmlAttribute. XmlElement, in particular, has a few interesting properties: IsNullable, DataType, ElementName, and Namespace. IsNullable lets you specify whether the property should be rendered even if set to null. DataType allows you to specify the XSD type of the element the serializer will generate. ElementName indicates the name of the element. Finally, Namespace associates the element with a namespace URI. If you want to use a namespace prefix, add a reference to that namespace using the XmlSerializerNamespaces class, as shown here:

[XmlElement(Namespace ="urn:mspress-xml", IsNullable=true, 
  DataType="nonNegativeInteger", ElementName="FamilyName")]

When the IsNullable property is set to true and the property has a null value, the serializer renders the element with a nil attribute that equals true, as shown here:

<x:FirstName xsi:nil="true" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />

If you specify the DataType attribute, the type name must match exactly the XSD type name. Specifying the DataType attribute does not actually change the serialization format, it affects only the schema for the member.

The XmlAttribute Attribute

The XmlAttribute attribute also supports the DataType and the Namespace properties. IsNullable is not supported. In addition, you can replace the default name of the attribute with the string assigned to the AttributeName property. As with elements, the default name of the attribute is the name of the parent class member.

The XmlEnum Attribute

If your class definition contains an enumeration type, the XmlEnum attribute lets you modify the named constants used to define each value member, as shown here:

public enum SeatsAvailable
{
    [XmlEnum(Name = "AisleSeat")]
    Aisle,
    [XmlEnum(Name = "CentralSeat")]
    Central,
    [XmlEnum(Name = "WindowSeat")]
    Window
}

You use the Name property to modify the name of the enum member.

The XML Schema Definition Tool

Installed as part of the .NET Framework SDK, the XML Schema Definition Tool (xsd.exe) has several purposes. When it comes to XML serialization, the tool is helpful in a couple of scenarios. For example, you can use xsd.exe to generate source class files that are the C# or Microsoft Visual Basic .NET counterpart of existing XSD schemas. In addition, you can make the tool scan the public interface exposed by managed executables (DLL or EXE) and extrapolate an XML schema for any of the contained classes.

In the first case, the tool automatically generates the source code of a .NET Framework class that is conformant to the specified XML schema. This feature is extremely handy when you are in the process of writing an application that must cope with a flow of XML data described by a fixed schema. In a matter of seconds, the tool provides you with either C# or Visual Basic source files containing a number of classes that, when serialized through XmlSerializer, conform to the schema.

Another common situation in which xsd.exe can help considerably is when you don’t have the source code for the classes your code manages. In this case, the tool can generate an XML schema document from any public class implemented in a DLL or an EXE.


Overriding Attributes

A fairly common scenario for XML serialization is when you call into middle-tier class methods, get back some XML data, and then map that information onto other classes. In real-world situations, you can’t control or modify the layout of the incoming XML data or the structure of the target classes.

This is certainly nothing new for experienced developers who have been involved in the design and development of distributed, multitiered systems. Normally, you resolve the issue by writing adapter components that use hard-coded logic to transform the inbound XML flow into fresh instances of the target classes. Although the map of the solution is certainly effective and reasonable, a number of submerged obstacles can make your trip through the data long and winding.

First you must parse the XML data and extrapolate significant information. Next you copy any pieces of information into a newly created instance of a target class. The XML serialization mechanism was designed to resolve this difficulty, thus making the process of initializing classes from XML data both effective and efficacious.

Adapting Data to Classes

Reading incoming XML data is itself a kind of deserialization. However, as we’ve seen, the XML deserializer can only re-create an instance of the type you pass when you create the XmlSerializer object. How can you comply with any difference in the schema of the target class and the incoming XML data? That task is handled by the attribute overrides process for the XMLSerializer object, shown in Figure 11-2.

Figure 11-2. Attribute overrides are crucial architectural elements to allow effective XML-to-class mapping.


The XML serializer works on top of a particular type—the target class. While deserializing, the deserializer engine attempts to fit incoming data into the properties of the target class, taking into careful account any attributes set for the various properties. What happens if the source and the destination follow incompatible schemas? This might seem a rather odd situation—how could you deserialize data that you haven’t previously serialized?—but in practice it exemplifies the real goal of XML serialization. Beyond any technological and implementation details, XML serialization is simply a way to automatically instantiate classes from XML data.

This is not simply the problem of transforming one schema into another; instead, you must transform a schema into a class. If you don’t want to write an ad hoc piece of code, you have only the following few options:

  • Modify the source data to make it fit the target class through default XML serialization. This solution is impractical if you don’t have access to the component that generates this flow.

  • Modify the target class with static attributes to make it support in deserialization the schema of the incoming data. This solution is impractical if you don’t have access to the source code for the class—for example, if the class is deployed through an assembly.

  • Override the attributes of the target class using dynamic hooks provided by the objects you can create and store in an XmlAttribute­Overrides class. We’ll examine this solution more closely in the section “The XmlAttributeOverrides Class,” on page 505.

  • If the differences involve data, too, and therefore can’t be addressed with schema elements, resort to deserialization events, as described in the section “Deserializing XML Data to Objects,” on page 496.

Attribute overriding is a technique that lets you change the default way in which serialization and deserialization occur. In addition to the case just mentioned, attribute overrides are also useful for setting up different (and selectable) serialization/deserialization schemes for a given class.

The XmlAttributeOverrides Class

You pass an instance of the XmlAttributeOverrides class to the XmlSerializer constructor. As a result, the serializer will use the data contained in the XmlAttributeOverrides object to override the serialization attributes set on the class. The XmlAttributeOverrides class is a collection and contains pairs consisting of the object types that will be overridden and the changes to apply.

As shown in the following code, you first create an instance of the XmlAttributes class—that is, a helper class that contains all the pairs of overriding objects. Next you create an attribute object that is appropriate for the object being overridden. For example, create an XmlElementAttribute object to override a property. In doing so, you can optionally change the element name or the namespace. Then store the override in the XmlAttributes object. Finally, add the XmlAttributes object to the XmlAttributeOverrides object and indicate the element to which all those overrides will apply.

// Create the worker collection of changes
XmlAttributes changes = new XmlAttributes();

// Add the first override (change the element’s name)
XmlElementAttribute newElem = new XmlElementAttribute();
newElem.ElementName = "New name";
changes.XmlElements.Add(newElem);

// Create the list of overrides
XmlAttributeOverrides over = new XmlAttributeOverrides();

// Fill the overrides list (Employee is the target class)
over.Add(typeof(Employee), "Element-to-Override", changes);

The instance of the XmlAttributeOverrides class is associated with the XML serializer at creation time, as shown here:

XmlSerializer ser = new XmlSerializer(typeof(Employee), over);

Note

Attribute overriding also enables you to use derived classes in lieu of the defined classes. For example, suppose you have a property of a certain type. To force the serializer (both in serialization and deserialization) to use a derived class, follow the steps outlined in the preceding code but also set the Type property on the overriding element, as shown here:

// Manager is a class that inherits from Employee
newElem.Type = typeof(Manager);


Attribute overriding is a useful technique, and in the next section, we’ll see it in action.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.235.210