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.
In all cases we discussed
so far, mapping a .NET class to an XML complexType was easy because the schema
provided an unambiguous mapping from one type system to the other. Now, there
are cases where the schema-type-to-.NET-type mapping is ambiguous, because the
XML schema type defines ambiguous child element with the <choice> model
group. Generally, a model group defines usage rules for a group of elements, or
in XML terminology particles. The rules of a model group define which particles
can occur in a group, in which order they occur and how many times. The
<choice> model group defines a group of mutually exclusive particles. We
have to examine this group a little bit deeper, because there is no true
counterpart to the choice model group in object oriented programming languages.
In a programming language each field of a class is present in each instance.
The <choice> group on the other hand defines a set of “fields” and only
one of them may be present in any given instance. It is up to the parsing logic
in the application processing the XML to interpret the semantic differences
between the elements. In the following section we learn how we design classes
to bridge the two type systems and process XML types defined with the
<choice> model group.
First let’s look at the
simple case where a <choice> defines a single occurrence of a single
element. The following schema describes a Car element with exactly four
elements. The first three are always Make, Model and Year. Every Car has
exactly one out of two additional elements because the <choice> occurs
exactly once (minOccurs=1, maxOccurs=1) and either element within the groups
occurs exactly once. The fourth element can be either a LeasePayment or a
FinancePayment element.
7
Listing 10.3 A schema
with a <choice> model group and two plossible instances of the Car_T type
We could design a .NET
class that maps to the Car type if the two elements were of different types.
One technique would be to declare a field of type object and attach XmlElement
attributes to resolve the two element names to the same field payment, but
attaching two XmlElement attributes in only possible if the two different
elements are of different types. LeasePayment and FinancePayment on the other
hand are of the same type, which negates applying two XmlElements, because
Serialize() cannot look at the type object the payment field refers to in order
to figure out if the field refers to a LeasePayment or a FinancePayment.
Equally, if we only provide a single field for the two payment types, Deserialize() can only store the amount in
the XML stream, but not which type of payment type the XML stream specified to.
What we need to solve this
problem is a second field to store the auxiliary information, which we will
call the “choice field”. In the example above the choice field would hold
information about the payment. The type of the choice field is an enumeration
of the elements in the <choice> that we need to disambiguate. The
Serialize() method can now consult the choice field whether to generate a
FinancePayment or a LeasePayment element when it processes a Car object. Likewise,
Deserialize() can set the choice field to convey whether the data was
deserialized from a LeasePayment or a FinancePayment. But wait, how does the
XmlSerializer know about the choice field? We have to identify a choice field
by attaching an XmlChoiceIdentifierAttribute to the field we need to
disambiguate. The XmlSerializer detects the attribute when it analyzes the type
and links the choice field to the data field.
Let’s develop a class that
maps to the XML type Car described by the schema above. Besides the fields for
make, model and year we also have to supply two fields for the payment field:
One for the data and one to indicate whether the data refers to a LeasePayment
or a FinancePayment element.
First we create a public
enumeration called ItemChoiceType with values named after the ambiguous
elements in the <choice> model group.
Then we create the Car
class with the two fields for the <choice>. The field Item to stores the
element data and ItemElementName to identifies whether the data is a
LeasePayment or a FinancePayment.
Next, we attach two
XmlElementAttribute attributes to the data field so Deserialize() will know to
store the data of either XML element in the Item field.
We also attach an XmlChoiceIdentifierAttribute
pointing to ItemElementName to clarify the particle Item refers to.
Deserialize() will set ItemElementName to reflect which <choice> element
was present in the deserialized XML stream, Serialize() will read
ItemElementName when it processes Item to determine what element to generate.
(annotation) <#1 The
enumeration defines a value for each of the ambiguous elements>
(annotation) <#2 We have
to attach an XmlIgnoreAttribute attribute to the ItemElementName field because it does not map to the
<choice> model group. It is only used by the XmlSerializer.>
TIP: While we can create
the class and the enumeration manually, but it is by far easier to let the XSD
schema definition tool provided with the Framework SDK generate the complete
class definition.
The more complicated case
to map a <choice> model group to a .NET class arises when the model group
can occur more than once in an instance of the schema type, i.e. if the
maxOccurs attribute on the group is greater than one. For example if we changed
the schema definition from listing 10.3 to the following:
Then there is no
restriction how many and in which order payment elements appear in a type,
because each payment element is viewed as an instance of the model group, which
can appear an “unbounded” number of times, like in the next XML fragment.
<Car>
<Make>Ford</Make>
<Model>Explorer</Model>
<Year>2002</Year>
<FinancePayment>699</FinancePayment>
<FinancePayment>799</FinancePayment>
<LeasePayment>429</LeasePayment>
<FinancePayment>899</FinancePayment>
</Car>
This may sound a bit
complicated, but what we learned in the previous section easily extends to
<choice> definitions allowing multiple occurrences. First of all, we need
to declare a data field of an array type to store all the data. Then we also
make the choice field an array to clarify which particle of the model group the
data items corresponds to. Once again, the type of the choice field has to be
an enumeration with values for the ambiguous particle names. The items in the
choice field array clarify the particle of the item in the choice array at the
same position. Finally we attach an XmlChoiceIdentifierAttribute to the data
field array to signal the XmlSerializer which two arrays contain the
information to map objects of this class to the <choice> model group.
The modifications to the
Car T class from the previous example to handle multiple occurrences of the
<choice> are as simple as changing the data field and the choice field to
array types as shown in the code fragment shown below: