The Microsoft .NET Framework has enabled developers to quickly build loosely coupled systems via Web services.
In addition to Web services, the .NET Framework provides other benefits for developers. Perhaps one of the most important sets of classes in the .NET Framework is ADO.NET. With ADO.NET, developers can significantly work with disconnected data easier than before. It also allows the data to maintain relationships and referential integrity.
So it would seem only natural that a .NET Web service that is working with a database would want to take advantage of the disconnected data features of ADO.NET, and specifically the DataSet class. Many .NET Web service demos have done just that, creating a Web service that returns a DataSet to be consumed by a UI of some sort, also written in .NET, which can easily bind the data to some controls.
To evaluate the overall solution, developers need to understand that Web services are meant to create technology agnostic sets of functionality -- any client, using any technology, can reuse the features contained in that service. For this to be a "good" Web service, it should be usable by other technologies. Let's take a look at the details of a simple Web service that returns a DataSet and see how it measures up.
After creating a simple WebMethod that returns a DataSet, look at the WSDL (Web Services Description Language) generated by the .NET Framework. Remember that WSDL describes the service, including method names, parameters and return values. This data is used by the .NET Framework WSDL.exe tool, along with other tools in other technologies, to create a proxy class for client to use when accessing the service. A detailed description of the types used as parameters and return values is essential for generating a useful proxy class.
Below is an excerpt from a simple Web service's WSDL that shows the return value of the GetCustomers WebMethod. Notice that GetCustomersResult is actually comprised of two types -- the first is an XML schema
<s:element name="GetCustomersResponse"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="GetCustomersResult"> <s:complexType> <s:sequence> <s:element ref="s:schema" /> <s:any /> </s:sequence> </s:complexType> </s:element> </s:sequence> </s:complexType> </s:element>
The first issue is that any tool that attempts to generate a proxy for this service has no way of knowing what to expect. Type checking will have to be done at runtime.
So how does the .NET Framework know when to convert the result into a DataSet for the client proxy? The key is in the actual XML generated from the call. Below is a subset of the XML that is returned when the WebMethod is called with only one row being returned.
<DataSet xmlns="http://tempuri.org/BadWS/Service1"> <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="NewDataSet" msdata:IsDataSet="true"> <xs:complexType> <xs:choice maxOccurs="unbounded"> <xs:element name="Table1"> <xs:complexType> <xs:sequence> <xs:element name="FirstName" type="xs:string" minOccurs="0" /> <xs:element name="LastName" type="xs:string" minOccurs="0" /> <xs:element name="Age" type="xs:int" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema> <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> <NewDataSet xmlns=""> <Table1 diffgr:id="Table11" msdata:rowOrder="0" diffgr:hasChanges="inserted"> <FirstName>Paul</FirstName> <LastName>Ballard</LastName> <Age>32</Age> </Table1> </NewDataSet> </diffgr:diffgram> </DataSet>
The schema definition has three columns in the table defined with their types, but notice the bolded sections. The specification for the schema includes a Microsoft specific namespace and then an attribute from that namespace called "IsDataSet" set to true -- the incoming data is actually an ADO.NET DataSet.
So then, a Java-based client application that has no concept of an ADO.NET DataSet is left having to work with the data strictly as XML. While this doesn't preclude interoperability altogether, it does make it significantly more difficult when the end result is just a list of customers with three fields.
By foregoing the use of a DataSet and using instead an array of custom types, this Web service can be significantly more useful and interoperable. An excerpt of the WSDL for the Web service created using custom types is below.
<s:element name="GetCustomers2Response"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="GetCustomers2Result" type="tns:ArrayOfCustomer" /> </s:sequence> </s:complexType> </s:element> <s:complexType name="ArrayOfCustomer"> <s:sequence> <s:element minOccurs="0" maxOccurs="unbounded" name="Customer" type="tns:Customer" /> </s:sequence> </s:complexType> <s:complexType name="Customer"> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="FirstName" type="s:string" /> <s:element minOccurs="0" maxOccurs="1" name="LastName" type="s:string" /> <s:element minOccurs="1" maxOccurs="1" name="Age" type="s:int" /> </s:sequence> </s:complexType>
Notice that it defines the GetCustomers2Result as an array of a complex type with clear definitions for each field. Now even a Java-based tool can generate a useful proxy that will allow the client developer to use our service much more easily.
Paul Ballard is the Editor of TheServerSide.NET Enterprise .NET Community. Paul is an MCSD, MCAD, and MCSE certified consultant with more than 15 years of experience designing and building Windows- and Web-based distributed applications and is currently specializing in Microsoft's .NET technologies as a consultant, speaker, and trainer. Paul is also a volunteer with INETA and the founder of the Central Pennsylvania .NET User Group.