Mark Wilson I am the creator of TopXML. I am available for international and local (Australia) contracts. I am a Solution Architect/Business Analyst. I have worked in IT in several countries (NZ, Australia, South Africa, UK) building and training teams for government and very large non-governmental organizations. I am ex-Microsoft Consulting Services. I wrote the first book on Microsoft XML published in 2000 called XML Programming with VB and ASP. Most recently I have been building tools for the SEO industry. Ask me for a 37 point SEO health-checkup for your website.
The way to serialize classes with customized XML builds on what
we have learned in the previous section. We can design classes with special
fields to store the custom XML. Fields of type XmlElement[], XmlDocument or
XmlDocumentFragment can store the extension part of an XML type defined with
the <any /> model. When we serialize an object of this class, the
XmlSerializer will serialize the XML content as well formed XML into the output
stream. We also need to attach an XmlAnyElement attribute to he field to
prevent an additional XML element around the XML stored inside.
Developing classes to map XML types with custom attributes works
just the same. The class needs a field which will store all custom attributes.
For the XmlSerializer to create extra attributes we have store the attributes
in an array of type XmlAttribute. Instead of an XmlAnyElement attribute we need
to attach an XmlAnyAttribute attribute and our class is ready to go. An example
featuring both, custom attributes and elements is the Body type of the SOAP
protocol from listing 10.4. Listing 10.5 shows a class, which provides the
extensibility through an XmlElement[] and an XmlAttribute[] field.
9
Listing 10.5 A class to
map an extensible XML type.
Deserializing extensible XML content is slightly trickier than
serializing it. By default, the XmlSerializer ignores any nodes that do not map
to a field in the class, but we can access any unmapped nodes on one of two
ways:
§ The
XmlSerializer stores unmapped nodes in designated fields of the deserialized
object.
§ The
XmlSerializer notifies the application in control of deserializing the XML
stream of unmapped XML content.
The two ways are mutually exclusive and the former takes
precedence over the latter.
The first approach we learn to gain access to unmapped nodes in
XML stream is designate fields in the deserialized class to receive the
unrecognized nodes. This approach works well for scenarios where we anticipate
XML documents with a fixed XML structure and some with customizable sections,
like in the example of the SOAP protocol. The XmlSerializer stores all extra
XML nodes in the designated fields of the deserialized object and the unmapped
nodes are available to the application as part of the deserialized object.
We designate the fields again by attaching the XmlAnyAttribut e or the
XmlAnyElement attribute to a field of type XmlNode[], XmlAttribute[] or
XmlElement[]. Unrecognized elements are added to the array tagged with the
XmlAnyElement attribute, unrecognized attributes are added to the array tagged
with the XmlAnyAttribute attribute.
NOTE: We can attach only one instance of the XmlAnyAttribute and the XmlAnyElementof
these attributes inside a class or the Deserialize() method would not know
where to store the extra nodes.
The designated fields receive only the unrecognized nodes found within the
scope of the deserialized class. Any unmapped nodes that are not immediate
children of the top level element for a class belong to a different class. The
XmlSerializer discards any unmapped nodes if the deserialized class does not
designate any fields to receive them. This sounds more complicated than it is.
Figure 10.1 illustrates how unrecognized nodes in an XML document, shown on the
right hand side, are mapped to the class hierarchy on the left hand side.
Figure 10.1 Fields
decorated with the XmlAnyArrayAttribute and the XmlAnyElementAttribute receive
XML nodes that do not map to a field or property. Nodes are discarded if they
do not directly map to a field and the currently deserialized object declared
no fields with one of these attributes is available.
On the left hand side of the figure we see how we could design a
class to process the extensible types from listing 10.4. These classes loosely
relate to XML messages formatted according to the SOAP protocol standard, but
they do not nearly reflect all features of a SOAP message. The SoapEnvelope
class is the root of the deserialized hierarchy. The SoapEnvelope class defines
a for the Body part of the message of the message. For optional other elements
in the Envelope the class defines the ExtraEnvelopeContent field with the
XmlAnyElement attribute attached. The SoapBody class does not define any fields
besides an XmlElement array for its content, because the XML schema fragment
from listing 10.4 does not define any mandatory elements.
On the right hand side of the figure we see an XML document that resembles a
SOAP message. When we pass this XML document to the Deserialize() method of the
XmlSerializer, it will store all content inside the Body in the BodyContent
field. The two id attributes are not part of the standard SOAP format; hence
none of the two classes defines fields for them. Both classes do define a field
for the XmlSerializer to store any attributes on their respective top level
elements, Envelope and Body. Note that the XmlSerializer would discard the Body’s
id element if the SoapBody class
would not provide its own field marked with the XmlAnyAttribute attribute.