10.6 Accessing Data for the Web Service

only for RuBoard

A production version of the ZipService would probably use a database like SQL Server or Oracle to provide data for the service. To keep the example self-contained, an XML file (shown in Example 10-7) containing zip code information is used. The file contains only partial data for two cities, but can easily be extended if desired.

Example 10-7. Partial zip code data for Houston and Austin
 <?xml version="1.0" ?> <cities>     <city name="Houston">         <zip>77002</zip>         <zip>77003</zip>         <zip>77004</zip>         <zip>77005</zip>         <zip>77006</zip>     </city>     <city name="Austin">       <zip>78742</zip>       <zip>78744</zip>       <zip>78746</zip>       <zip>78748</zip>       <zip>78750</zip>     </city> </cities> 

10.6.1 Typed DataSet

The implementation does not use the XML directly. Instead, a typed DataSet is created to handle data access. A DataSet provides a consistent relational programming model that is independent of a data source. This means that, once a DataSet is populated , where the data came from is irrelevant. By coding against a DataSet , the data source can be changed at a later time without negatively impacting the web service

The XML Schema Definition Tool that ships with the .NET Framework SDK ( xsd.exe ) does most of the work. This tool can generate an XSD schema that describes the XML in Example 10-7. In turn , that schema can build a derived DataSet class that allows the XML to be manipulated programmatically.

Building a DataSet with this tool is a three-phase process. The first step is to generate a schema from the XML. To do this, run xsd from the command line and pass the name of the XML file as an argument:

 xsd zipcodes.xml 

This produces a file called zipcodes.xsd that contains the schema shown in Example 10-8.

Example 10-8. zipcodes.xsd
 <?xml version="1.0" encoding="utf-8"?> <xs:schema id="cities"             xmlns=""             xmlns:xs="http://www.w3.org/2001/XMLSchema"             xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">   <xs:element name="cities" msdata:IsDataSet="true">     <xs:complexType>       <xs:choice maxOccurs="unbounded">         <xs:element name="city">           <xs:complexType>             <xs:sequence>               <xs:element name="zip"                            nillable="true"                            minOccurs="0"                            maxOccurs="unbounded">                 <xs:complexType>                   <xs:simpleContent msdata:ColumnName="zip_Text"                                      msdata:Ordinal="0">                     <xs:extension base="xs:string">                     </xs:extension>                   </xs:simpleContent>                 </xs:complexType>               </xs:element>             </xs:sequence>             <xs:attribute name="name"                            type="xs:string" />           </xs:complexType>         </xs:element>       </xs:choice>     </xs:complexType>   </xs:element> </xs:schema> 

If the DataSet were created directly from the schema, the following classes would be created: cities (the DataSet ), cityDataTable , cityRow , zipDataTable , and zipRow . Two event classes are also created, zipRowChangeEvent and zipRowChangeEvent . In essence, a class is created that represents a table in a database along with a corresponding class that represents a row of data from that table. Two events are defined that will be fired when data in those rows change. Internally, a parent-child relationship is defined between the two tables.

Because the zip is defined by an XML element, it gets the moniker zip_Text . The city name is described by the name attribute (as opposed to an element). So, to get a zip code, a call to zipRow.zip_Text is used, compared to getting a city name by calling cityRow.name . Very inconsistent!

Using the schema at this point to create a DataSet results in the generation of some very ugly source code, as the following testifies:

 Dim city As cities.cityRow Dim row As System.Data.DataRow For Each row In cityZipDS.city.Rows   city = CType(row, cities.cityRow)   If city.name = "Houston" Then     Dim zipRow As cities.zipRow     Dim zipRows( ) As cities.zipRow = city.GetzipRows     For Each zipRow In zipRows       If zipRow.zip_Text = "77004" Then         Return True       End If     Next zipRow   End If Next row 

The XSD really needs to be tweaked before the DataSet is created. Rather than thinking of the data in terms of data or a row, a collection analogy is much bettera Cities class that contains a collection of City that, in turn, contains a collection of Zipcode . Getting the city name and zip code through a consistent Text property would also be nice.

The second phase modifies the XSD output by the tool. Before anything can happen, an additional namespace must be added to the schema:


This namespace makes several annotations available to the schema. Table 10-1 lists the ones that are pertinent to the discussion.

Table 10-1. DataSet annotations




Object name


Object collection name


Name of the object when used in a parent relationship


Name of the method to return child objects

Consider the city element here:

 <xs:element name="city"              codegen:typedName="City"              codegen:typedPlural="Cities"> 

By using the typedName and typedPlural annotations with this element, the cityRow class becomes the City class and the Rows property of the primary DataSet class becomes Cities .

Now, when a city is used in a singular context, whether it is a property name or a class name, " City " is used. In the plural, " Cities " is used:

 Dim cityZipDataSet As cities . . . Dim city As cities.City For Each city In cityZipDS.Cities   'Do something city-like here Next city 

Example 10-9 contains the complete listing for the modified XSD that uses the typedName and typedPlural annotations.

Example 10-9. Schema for annotated DataSet
 <?xml version="1.0" encoding="utf-8"?> <xs:schema id="cities"      xmlns=""      xmlns:xs="http://www.w3.org/2001/XMLSchema"      xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"     xmlns:codegen="urn:schemas-microsoft-com:xml-msprop">   <xs:element name="cities" msdata:IsDataSet="true">     <xs:complexType>       <xs:choice maxOccurs="unbounded">  <xs:element name="city  "  codegen:typedName="City  "  codegen:typedPlural="Cities">  <xs:complexType>             <xs:sequence>  <xs:element name="zip  "  nillable="true  "  minOccurs="0  "  maxOccurs="unbounded  "  codegen:typedName="Zip  "  codegen:typedPlural="Zipcodes">  <xs:complexType>  <xs:simpleContent msdata:ColumnName="zip_Text  "  msdata:Ordinal="0  "  codegen:typedName="Text">  <xs:extension base="xs:string">                     </xs:extension>                   </xs:simpleContent>                 </xs:complexType>               </xs:element>             </xs:sequence>  <xs:attribute name="name" type="xs:string  "  codegen:typedName="Text" />  </xs:complexType>         </xs:element>       </xs:choice>     </xs:complexType>   </xs:element> </xs:schema> 

Once the XSD has been modified to produce aesthetic output, the last phase is the creation of the DataSet . This is done by feeding the schema back to xsd.exe . This time, a few additional command-line parameters are necessary:

 xsd /dataset /language:VB zipcodes.xsd 

/dataset tells xsd to build a DataSet . An alternative switch is /classes , which causes a set of collection classes to be created. /language determines what language the output file will be written in. This will produce a file called zipcodes.vb that can be compiled with the web service.

To get a better feel for what annotations actually accomplish, try creating a DataSet from Example 10-8 and then another from Example 10-9. Do some comparative analysis.

10.6.2 ZipService.IsValid

Now that a typed DataSet exists, IsValid can finally be implemented. To get the XML into the cities dataset , an instance of StreamReader is used to create a file stream to the XML. The StreamReader is then used to create an XMLTextReader that reads the XML from the stream:

 Dim cityZipDS As cities    Dim xmlFile As String = AppDomain.CurrentDomain.BaseDirectory & _   "/bin/zipcodes.xml"     Dim reader As New XmlTextReader(New StreamReader(xmlFile)) 

Finally, an XMLSerializer serializes the XML from the reader into cities , and the reader is closed:

 Dim serializer As New XmlSerializer(GetType(cities)) cityZipDS = CType(serializer.Deserialize(reader), cities) reader.Close( ) 

At this point, navigating the data is similar to navigating a collection:

 Dim city As cities.City For Each city In cityZipDS.Cities   If city.Text = "Austin " Then     Dim zipcode As cities.Zip     Dim zipcodes( ) = city.GetZipcodes     For Each zipcode In zipcodes       If zipcode.Text = "78756" Then         Return True       End If     Next zipcode   End If Next ct 

The final listing for ZipService is in Example 10-10. However, there is a major problem with the implementation. The data is reloaded into cities every time IsValid is called. Remember, calling a web method is just like SingleCall in remoting. The entire object is created and destroyed with every call. The challenge here is finding a way to cache the data so that performance will not be hindered. This implementation actually works well with a few simultaneous callers , but overall, it lacks scalability. There are probably several ways to fix this, but one suggestion is to move the implementation to a Singleton and make the web service a client of that object. The data is fairly static; new zip codes are not created every day. This way, the data would be loaded as needed. The ZipService class would really become a wrapper class to the Singleton implementation.

Example 10-10. The final version of ZipService.vb
 'vbc /t:library /r:System.dll  '/r:System.Web.Services.dll ZipService.vb     Imports System Imports System.Diagnostics Imports System.IO Imports System.Web.Services Imports System.Web.Services.Description Imports System.Web.Services.Protocols Imports System.Xml Imports System.Xml.Serialization     <WebService(Namespace:="", _ Description:="This service provides city/zipcode authentication")> _ Public Class ZipService        <WebMethod( ), SoapRpcMethod( )> _   Public Function IsValid(ByVal City As String, _                           ByVal Zip As String) As Boolean            Dim cityZipDS As cities        Dim xmlFile As String = AppDomain.CurrentDomain.BaseDirectory + _       "/bin/zipcodes.xml"         Dim reader As New XmlTextReader(New StreamReader(xmlFile))         Dim serializer As New XmlSerializer(GetType(cities))     cityZipDS = CType(serializer.Deserialize(reader), cities)     reader.Close( )         Dim ct As cities.City     For Each ct In cityZipDS.Cities       If ct.Text = City Then         Dim zipcode As cities.Zip         Dim zipcodes( ) = ct.GetZipcodes         For Each zipcode In zipcodes           If zipcode.Text = Zip Then             Return True           End If         Next zipcode       End If     Next ct         Return False       End Function     End Class 
only for RuBoard

Object-Oriented Programming with Visual Basic. Net
Object-Oriented Programming with Visual Basic .NET
ISBN: 0596001460
EAN: 2147483647
Year: 2001
Pages: 112
Authors: J.P. Hamilton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net