Previous Section  < Day Day Up >  Next Section

18.5. Using Web Services with Complex Data Types

The BirthDayWS Web Service used throughout this chapter accepts integers as input and returns a string value. This is useful for introducing Web Service principles because HTTP GET, HTTP POST, and SOAP all support it. However, Web Services also have the capability of serving up more complex data types such as data sets, hash tables, images, and custom objects.

Before data can be sent to or from a Web Service, it is serialized using XML serialization. Conversely, it is deserialized on the receiving end so it can be restored to its original type. As we saw in Chapter 4, "Working with Objects in C#," not all data can be serialized. Thus, when designing a Web Service, it is important to understand restrictions that apply to serialization:

  • XML serialization can only be used with classes that contain a public parameterless constructor. For example, you may have a Web Service that returns a hash table because it has the constructor public Hashtable(). On the other hand, the Bitmap class does not have a parameterless constructor and cannot be used as a return type.

  • Read-only properties in a class cannot be serialized. The property must have a get and set accessor and be public.

  • Fields must be public to be serialized; private ones are ignored.

In this section, we work with two Web Service examples that illustrate the use of complex data. Our first example creates a Web Service that accepts the name of an image and returns it as a byte stream. The second example creates a client to use the Amazon Web Services provided by Amazon.com, Inc. These services offer a rich梑ut practical梥ampling of accessing multiple Web methods and processing a wide variety of custom classes.

A Web Service to Return Images

Image manipulation usually requires representing an image as a Bitmap object. However, because bitmaps cannot be serialized and transferred directly, we must find an indirect way to transport an image. The not-so-difficult solution is to break the image into bytes and return a byte stream to the client, who is responsible for transforming the stream to an image.

The logic on the server side is straightforward: a FileStream is opened and associated with the image file. Its contents are read into memory and converted to a byte array using


tempStream.ToArray()


This byte array is then sent to the Web client (see Listing 18-6).

Listing 18-6. Web Service to Return an Image as a String of Bytes

<%@ WebService Language="C#" Class="WSImages" %>

using System;

using System.Web.Services;

using System.IO;

using System.Web.Services.Protocols;

public class WSImages: System.Web.Services.WebService {

   [WebMethod(Description="Request an Image")]

   public byte[] GetImage(string imgName) {

      byte[] imgArray;

      imgArray = getBinaryFile("c:\\"+imgName+".gif");

      if (imgArray.Length <2)

      {

         throw new SoapException(

           "Could not open image on server.",

            SoapException.ServerFaultCode);

      } else

      {

         return(imgArray);

      }

   }

   public byte[] getBinaryFile(string filename)

   {

      if(File.Exists(filename)) {

         try {

            FileStream s = File.OpenRead(filename);

            return ConvertStreamToByteBuffer(s);

         }

         catch(Exception e)

         {

            return new byte[0];

         }

      } else { return new byte[0]; }

   }

   // Write image to memory as a stream of bytes

   public byte[] ConvertStreamToByteBuffer(Stream imgStream) {

      int imgByte;

      MemoryStream tempStream = new MemoryStream();

      while((imgByte=imgStream.ReadByte())!=-1) {

         tempStream.WriteByte(((byte)imgByte));

      }

      return tempStream.ToArray();  // Convert to array of bytes

   }

}


Our client code receives the byte stream representing an image and reassembles it into a Bitmap object. Because the Bitmap constructor accepts a stream type, we convert the byte array to a MemoryStream and pass it to the constructor. It can now be manipulated as an image.


WSImages myImage = new WSImages();

try {

   // Request an image from the Web Service

   byte[] image = myImage.GetImage("stanwyck");

   MemoryStream memStream = new MemoryStream(image);

   Console.WriteLine(memStream.Length);

   // Convert memory stream to a Bitmap

   Bitmap bm = new Bitmap(memStream);

   // Save image returned to local disk

   bm.Save("c:\\bstanwyck.jpg",

        System.Drawing.Imaging.ImageFormat.Jpeg);

}

catch (WebException ex)

{

   Console.WriteLine(ex.Message);

}


Using Amazon Web Services

To use the Amazon E-Commerce Service, you must register for a developer's token, which is required as part of all requests made to the Web Services. In addition, you should download (http://www.amazon.com/webservices) the developer's kit that contains the latest documentation, examples, and梞ost importantly梐 WSDL file defining all the services.

An examination of the WSDL file reveals that AmazonSearchService is the Web Service class that contains the numerous search methods available to clients. These methods provide the ability to search the Amazon product database by keyword, author, artist, ISBN number, manufacturer, actor, and a number of other criteria. Each search method takes a search object as an argument that describes the request to the server and returns a ProductInfo object. For example, a request to search by keywords looks like this:


AmazonSearchService amazon = new AmazonSearchService();

KeywordRequest kwRequest = new KeywordRequest();

// Set fields for kwRequest

ProductInfo products = amazon.KeywordSearchRequest(kwRequest);


Sending a Request with the AmazonSearchService Class

Table 18-3 contains a sampling of the methods available for searching Amazon products. These methods are for accessing the Web Service synchronously. An asynchronous form of each method is also available that can be accessed using the techniques discussed earlier in this chapter.

Table 18-3. Selected Methods of AmazonSearchService Class

Method

Description


ProductInfo KeyWordSearchRequest

 (KeywordRequest req)


Method to return items that contain one or more keywords provided in request.


ProductInfo AsinSearchRequest

 (AsinRequest req)


Method to return a book having a requested Amazon Standard Identification Number (ASIN) that is the same as the book's ISBN. Represented as a 10-digit string.


ProductInfo AuthorSearchRequest

(AuthorRequest req)


Method to return names of all books by requested author.


ProductInfo ActorSearchRequest

(ActorRequest req)


Method to return video titles of movies in which a specified actor or actress was a cast member.


ProductInfo PowerSearchRequest

(PowerRequest req)


Method to retrieve book information based on a Boolean query that may include a combination of title, subject, author, keyword, ISBN, publisher, language, and publication date (pubdate).


Each call to a Web method passes an object that describes the search request. This object is different for each method梖or example, AuthorSearchRequest requires an AuthorRequest object, whereas KeyWordSearchRequest requires an instance of the KeywordRequest class. These classes expose almost identical fields. Each contains a unique string field that represents the search query, five other required fields common to each class, and some optional fields for sorting or specifying a locale. Table 18-4 lists unique and shared fields for each method listed in Table 18-3.

Table 18-4. Selected Fields for Classes That Define a Search Request

Field

Description


KeywordRequest.Keyword

AsinRequest.Asin

ActorRequest.Actor

AuthorRequest.Author

PowerRequest.Power


These are string values containing the search value or query. For example:


PowerRequest pr = new PowerRequest();

   pr.Power = "author:Nabokov and

   keyword:butterfly";


string page

Page of results to display.

string mode

Type of products being searched梖or example, "books".

string tag

Amazon associate's ID. Use "webservices-20" as default.

string type

"lite" or "heavy". Determines how much XML data is returned.

string devtag

Developer's token assigned to you by Amazon.


Using the ProductInfo Class to Process the Web Service Response

The Web Service responds to the search request with a ProductInfo object containing results from the search. This object exposes three important fields: a TotalResults string contains the number of products retrieved by the request, a TotalPages string that indicates how many pages these results are displayed in, and the important Details array that contains a detailed description of products that constitute one returned page of results. This array is of the Details type. Table 18-5 shows the fields that are related to books.

Table 18-5. Selected Fields of the Details Class

Field

Description

string ProductName

Name of a single product.

string SalesRank

Ranking of product based on sales of items of its type.

string Publisher

Publisher of book.


String ListPrice

string OurPrice


List and sales price of book.

Reviews[] Reviews

The Reviews class contains several fields relating to


reviews of the book:

string AvgCustomerRating

string TotalCustomerReviews

CustomerReview CustomerReviews

string Comment

string Rating


String[] Authors

One or more authors for the book.


This is only a small sample of the fields available in the Details class. There is a particularly rich set of fields worth exploring that define video products.

Creating a Proxy for the Amazon Web Services

Our first step is to create a proxy class from the Amazon WSDL information. The downloadable kit includes a WSDL file, and it can also be retrieved from the Internet, as we do here. Using the VS.NET command line, we place the proxy source code in the file AZProxy.cs.


wsdl.exe  /out:c:\client\AZProxy.cs

      http://soap.amazon.com/schema3/AmazonWebServices.wsdl


Next, we create an assembly, AZProxy.dll, containing the proxy that will be used by client code. It is linked to assemblies containing .NET Web classes required by the application.


csc/t:library /r:system.web.services.dll /r:system.xml.dll

      AZProxy.cs


You can make a quick test of the service using this barebones application, azclient.cs:


using System;

using System.Web.Services;

namespace webclient.example {

public class AmazonClient

{

   static void Main(string[] args)

   {

      // Search for books matching keyword "butterflies"

      AmazonSearchService amazon = new AmazonSearchService();

      KeywordRequest kwRequest = new KeywordRequest();

      kwRequest.keyword = "butterflies";

      kwRequest.type = "heavy";

      kwRequest.devtag= "*************"; // your developer token

      kwRequest.mode = "books";       // search books only

      kwRequest.tag = "webservices-20";

      kwRequest.page = "1";          // return first page

      ProductInfo products =

          amazon.KeywordSearchRequest(kwRequest);

      Console.WriteLine(products.TotalResults);  // Results count

   }

}

}


Compile and execute this from the command line:


csc /r:AZProxy.dll azclient.cs

azclient


When azclient.exe is executed, it should print the number of matching results.

Building a WinForms Web Service Client

Let's design a Windows Forms application that permits a user to perform searches on books using multiple search options. Open VS.NET and select a Windows application. After this is open, we need to add a reference to the proxy assembly AZProxy.dll. From the menu, select Project ?Add Reference ?Browse. Click the assembly when it is located and then click OK to add it as a reference. You also need to add a reference to System.Web.Services.dll, which contains the required Web Service namespaces.

The purpose of the application is to permit a user to search the Amazon book database by keyword, author, or title. The search can be on a single field on a combination of fields. Figure 18-7 shows the interface for entering the search values and viewing the results. The Search buttons submit a search request based on the value in their corresponding text box. The Power Search button creates a query that logically "ands" any values in the text boxes and submits it.

Figure 18-7. Overview of how a client accesses a Web Service


A single page of results is displayed in a ListView control. Beneath the control are buttons that can be used to navigate backward and forward through the results pages.

Each Search button has a Click event handler that calls a method to create an appropriate request object and send it to the Amazon Web Service. A successful call returns a ProductInfo object containing information about up to 10 books meeting the search criteria. Listing 18-7 displays code that creates an AuthorRequest object, sends it to the Web Service, and calls FillListView to display the results in the ListView control.

Listing 18-7. Client Code to Display Results of Author Search?tt>azwsclient.cs

// Fields having class-wide scope

int CurrPg;               // Current page being displayed

string SearchMode = "";   // Current search mode

int MaxPages =1;          // Number of pages available

// This method is called when author Search button is clicked

private bool AuthorReq()

{

   AmazonSearchService amazon = new AmazonSearchService();

   AuthorRequest auRequest = new AuthorRequest();

   auRequest.author = textBox2.Text;   // Get author from GUI

   auRequest.type = "heavy";

   auRequest.devtag= "****KLMJFLGV9";  // Developer token

   auRequest.mode = "books";

   auRequest.tag = "webservices-20";

   auRequest.page = CurrPg.ToString();

   try

   {

      // Call Web Service with author query

      ProductInfo products =

            amazon.AuthorSearchRequest(auRequest);

      FillListView(products);

      return(true);

   }

   catch (SoapException ex)

   {

      MessageBox.Show(ex.Message);

      return(false);

   }

}

private void FillListView(ProductInfo products)

   listView1.Items.Clear();   // Remove current entries

   label6.Text="";            // Clear any title

   label4.Text = products.TotalResults;

   label5.Text = CurrPg.ToString()+" of "+products.TotalPages;

{

   MaxPages =  Convert.ToInt32(products.TotalPages);

   ListViewItem rowItem;

   string auth,rev;

   for (int i=0; i< products.Details.Length; i++)

   {

      rowItem = new

            ListViewItem(products.Details[i].ProductName);

      // Add Author. Make sure author exists.

      object ob = products.Details[i].Authors;

      if (ob != null)  auth =

            products.Details[i].Authors[0]; else auth="None";

      rowItem.SubItems.Add(auth);

      // Add Price

      rowItem.SubItems.Add(products.Details[i].OurPrice);

      // Add Average Rating

      ob = products.Details[i].Reviews;

      if (ob != null)  rev =

            products.Details[i].Reviews.AvgCustomerRating;

            else rev="None";

      rowItem.SubItems.Add(rev);

      // Add Date Published

      rowItem.SubItems.Add(

            products.Details[i].ReleaseDate);

      listView1.Items.Add(rowItem);

   }

}


The keyword, title, and power searches use an identical approach: Each has a routine comparable to AuthorReq that creates its own request object. The only significant difference pertains to the power search that creates a Boolean query from the search field values. The format for this type query is field:value AND field2:value AND field3:value. For example:


"author:hemingway AND keywords:Kilimanjaro"


This application was designed as a Windows Forms application. It could just as easily be set up as Web page under ASP.NET. The code to reference the assembly AZProxy.dll is identical. The ListView control is not supported on a Web Form, but you could easily substitute a DataGrid for it.

    Previous Section  < Day Day Up >  Next Section