There's been lots of debate about use of DataSets with web services. The issue is interoperability: the default WSDL representation of a DataSet is an arbitrary schema followed by arbitrary XML. That's just a bit on the loose side for most toolkits to deal with in the general case. However, one use case that drives the desire to use a DataSet in the first place is that the type of data to be returned is not known precisely until runtime. Lately, I discovered yet another corner of XmlSerializer that helps me deal with some of these situations without having to resort to using a DataSet.
It turns out that sometimes, although you don't know the exact type of data you'll be returning, you do know that it's going to be one of a known set of types. This is particularly common when you're returning the results of a database query: you might know that all your columns are either strings, booleans, integers, dates, or floating point numbers. If that's the case, there's an XML schema construct that allows you to say, “I want to have one of the following elements appear here.” It's called <xs:choice>, and here's a schema fragment that demonstrates its use:
<xs:element name=“item“>
<xs:complexType>
<xs:choice>
<xs:element minOccurs=“1“ maxOccurs=“1“ name=“string“ type=“xs:string“ />
<xs:element minOccurs=“1“ maxOccurs=“1“ name=“integer“ type=“xs:int“ />
<xs:element minOccurs=“1“ maxOccurs=“1“ name=“boolean“ type=“xs:boolean“ />
<xs:element minOccurs=“1“ maxOccurs=“1“ name=“date“ type=“xs:dateTime“ />
<xs:element minOccurs=“1“ maxOccurs=“1“ name=“float“ type=“xs:float“ />
</xs:choice>
</xs:complexType>
</xs:element>
What this schema means is that when an “item” element appears, it must have as a child exactly one of the elements <string>, <integer>, <boolean>, <date>, or <float>. Further, if the child element is <string>, the contents of that element must be a string. But if the child element is <integer>, then the contents must be an integer, and so on and so forth. It's a reasonably “normal“ bit of schema, and while I have no idea what the support in various non-.NET toolkits looks like, support for it is a lot more likely to exist than for the vagaries of DataSet.
From an XML standpoint, this is a pretty nice thing. It lets us express exactly what we want, namely, that we'll tell you at runtime what our choice for the type of the data is. Where this gets really great is that it's supported by System.Xml.Serialization, which powers the ASP.NET Web Services stack.
Here's how it maps: you define a class with the usual attributes from the System.Xml.Serialization namespace. But when you get to the element that you want to represent as an <xs:choice>, you simply use more than one [XmlElement] attribute, and you use the overload that lets you specify a type. Here's what I mean:
[XmlRoot(”item”)]
public class Item
{
private object value;
[XmlElement(“string“, typeof(string))]
[XmlElement(“integer“, typeof(int))]
[XmlElement(“boolean“, typeof(bool))]
[XmlElement(“date“, typeof(DateTime))]
[XmlElement(“float“, typeof(float))]
public object Value
{
get { return value; }
set { this.value = value; }
}
}
When the serializer encounters this type during either serialization or deserialization, it will use the attributes to control the mapping between element names and .NET types. Which is to say, when the Value is a float, you'll get <float>, when the Value is string, you'll get a <string>, and (better still) vice versa! You have to do a type test and typecast at the other end, like this:
if (foo.Value is string) { DealWithString((string) foo.Value); }
else if (foo.value is float) { DealWithFloat((float) foo.Value); }
// etc.
But big deal. :)
By itself, this is a fairly powerful feature. And there's still more we can do, but I think I'll stop this entry now while this entry is still reasonably short. More later.