Dealing with an XSD Choice when XSD.exe doesn’t add XmlChoiceIdentifierAttribute
On my current project we are dealing with some quite complex xsd schemas. Whilst working with one of these I was confused by what I was seeing in the generated code produced by xsd.exe; specifically where an xs:choice was involved. I was expecting to see:
- An XmlChoiceIdentifier attribute appear on the object in question
- An ItemChoice enumeration that identified the possible element names the choice provided
- An ItemElementName property of enum ItemChoice indicating which choice had been picked
For example, I was expecting the following xsd:
<xs:complexType name="MyComplexType">
<xs:choice>
<xs:element name="MyChoiceOne" type="xs:string" />
<xs:element name="MyChoiceTwo" type="xs:date" />
</xs:choice>
</xs:complexType>
to generate the following code:
[...]
public partial class EndPointChoice1
{
private object itemField;
private ItemChoiceType1 itemElementNameField;
[XmlElementAttribute("MyChoiceOne", typeof(System.DateTime), DataType="date")]
[XmlElementAttribute("MyChoiceTwo", typeof(string))]
[XmlChoiceIdentifierAttribute("ItemElementName")]
public object Item
{
get
{
return this.itemField;
}
set
{
this.itemField = value;
}
}
[XmlIgnoreAttribute()]
public ItemChoiceType1 ItemElementName
{
get
{
return this.itemElementNameField;
}
set
{
this.itemElementNameField = value;
}
}
}
[...]
public enum ItemChoiceType1
{
MyChoiceOne,
MyChoiceTwo,
}
When, in actuality, what you get is the above, minus the highlighted yellow source. With some extensive digging it appears that when generating source for an xs:choice using xsd.exe the following rules apply:
No of choices |
Number of types |
ItemChoice enum |
XmlChoiceIdentifierAttribute |
Return type |
|
Example 1 |
3 or more |
1 |
True |
True |
System.Double |
Example 2 |
3 or more |
2 or more |
True |
True |
System.Object |
Example 3 |
2 |
1 |
True |
True |
System.Double |
Example 4 |
2 |
2 |
False |
False |
System.Object |
What we are seeing here is that for every combination of choices and types apart from where you have 2 choices of different types, you'll get the expected output of an XmlChoiceIdentifierAttribute on the item and an ItemChoice enumeration generated. Whilst seemingly unintuitive at first, this does make some sense. In all cases, as a developer, you need to know enough about the schema to code for the different possibilities that can arise. In the first 3 examples this means making use of the ItemChoice enumeration to identify the correct code path. In the scenario of 2 choices of different types you make the choice based on the type of the object you are processing.
In order to test this conclusion, you can use the schema below with xsd.exe and the /classes flag. By inspecting the generated C# source code you will see that the code generated maps directly to the table above:
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="ChoiceExample"
targetNamespace="https://choiceExample.org/choices"
xmlns=" https://choiceExample.org/choices "
xmlns:mstns=" https://choiceExample.org/choices "
xmlns:xs="https://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<xs:complexType name="Example1">
<xs:choice>
<xs:element name="choice1" type="xs:double" />
<xs:element name="choice2" type="xs:double" />
<xs:element name="choice3" type="xs:double" />
</xs:choice>
</xs:complexType>
<xs:complexType name="Example2">
<xs:choice>
<xs:element name="choice1" type="xs:double" />
<xs:element name="choice2" type="xs:double" />
<xs:element name="choice3" type="xs:string" />
</xs:choice>
</xs:complexType>
<xs:complexType name="Example3">
<xs:choice>
<xs:element name="choice1" type="xs:double" />
<xs:element name="choice2" type="xs:double" />
</xs:choice>
</xs:complexType>
<xs:complexType name="Example4">
<xs:choice>
<xs:element name="choice1" type="xs:double" />
<xs:element name="choice2" type="xs:string" />
</xs:choice>
</xs:complexType>
<xs:element name="example1" type="Example1"></xs:element>
<xs:element name="example2" type="Example2"></xs:element>
<xs:element name="example3" type="Example3"></xs:element>
<xs:element name="example4" type="Example4"></xs:element>
</xs:schema>
When actually dealing with an object from the generated class you'd work with it as follows.
For examples 1 – 3 you switch over the 'ItemElementName' property:
switch(example.ItemElementName)
{
case ItemChoiceType.choice1:
this.item = (double)example.Item;
break;
case ItemChoiceType.choice2:
this.item = (string)example.Item;
break;
}
For example 4 you check the type of the Item and fork based on the result:
if(example.Item is double)
{
this.item = (double)p.Item;
}
else
{
this.item = (string)p.Item;
}
One of the things I'm working on at the moment is to create a few extension methods to make this easier. I'll post those when they are done.
Comments
Anonymous
August 12, 2014
Did you make any progress how to handle the choices in C#?Anonymous
May 23, 2015
Simon, I too am curious if you ended up writing the extension classes you thought about. Cameron