[ Team LiB ] Previous Section Next Section

16.4 Creating the Web Services Client

By the time you read this book, the code shown in Example 16-2 might not work. If Amazon has changed the layout of the book page, the parsing code could easily break. Screen-scraping is notoriously fragile. Fortunately, however, Amazon has provided a web service with methods that allow you to acquire the information you need based on ISBN or other criteria.

In the first revision of the desktop application, you'll strip out all the screen scraping code and replace it with a web services client. To make this work, however, you'll need to download the Amazon Web Services developer kit, available at http://www.amazon.com/webservices, and as shown in Figure 16-5.

Figure 16-5. Amazon.com Web Services
figs/pcsharp3_1605.gif

The key is to create a proxy class to use with your client. You do so by obtaining the WSDL document that Amazon supplies, and then compiling that with the command-line instruction:

wsdl /o:Amazon.cs AmazonWebServices.wsdl

Your next step is to add the resulting file (Amazon.cs) to your new project, which will, among other things, declare the ProductInfo class (an instance of which you'll create in your own class). To get started, create a duplicate of the project shown in Example 16-2, and call it SalesRankDBWebServices, as shown in Example 16-5.

Example 16-5. SalesRankDBWebServices
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Web.Services;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;

// use Amazon web service rather than
// Screen Scraping
namespace SalesRankDBWebService
{
   public class Form1 : System.Windows.Forms.Form
   {
      private System.Windows.Forms.Button btnStart;

      private string connectionString;
      private System.Data.SqlClient.SqlConnection connection;
      private System.Data.SqlClient.SqlCommand command;
      private System.Windows.Forms.Timer timer1;
      private System.ComponentModel.IContainer components;
      private int timeRemaining;
      private System.Windows.Forms.Button btnNow;
      private System.Windows.Forms.TextBox txtClock;
      private System.Windows.Forms.ListBox lbOutput;
      const int WaitTime =  900; // 15 min.

      public Form1( )
      {
         InitializeComponent( );
      }

      /// <summary>
      /// Clean up any resources being used.
      /// </summary>
      protected override void Dispose( bool disposing )
      {
         if( disposing )
         {
            if (components != null) 
            {
               components.Dispose( );
            }
         }
         base.Dispose( disposing );
      }

      #region Windows Form Designer generated code
      #endregion

      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
      static void Main( ) 
      {
         Application.Run(new Form1( ));
      }

      private void Form1_Load(object sender, System.EventArgs e)
      {
         // connection string to connect to the Bugs Database
         connectionString = 
             "server=localhost;Trusted_Connection=true;database=AmazonSalesRanks";

         // Create connection object, initialize with 
         // connection string. Open it.
         connection = 
            new System.Data.SqlClient.SqlConnection(connectionString);
       
         // Create a SqlCommand object and assign the connection
         command = 
            new System.Data.SqlClient.SqlCommand( );

         command.Connection = connection;
         timer1.Interval = 1000; // one second 
         timer1.Enabled = false;
         timeRemaining = 2;  // how many seconds until update
         // UpdateTextFields( );
         lbOutput.Items.Add("Version 1.10");
         UpdateButton( );
      
      }

      private void UpdateButton( )
      {
         btnStart.Text = timer1.Enabled ? "Stop" : "Start";
      }

      private void btnStart_Click(object sender, System.EventArgs e)
      {
         timer1.Enabled = timer1.Enabled ? false : true;
         UpdateButton( );
      }

      // this method changes considerably
      private void GetInfoFromISBN(string isbn, string technology)
      {
         if (isbn.Length != 10)
            return ;
         ProductInfo pi = null;  // declare the ProductInfo object

         // initialize the local variables
         string title = "";
         string author = "";
         string publisher = "";
         string pubDate = "";
         int rank = 9999999;
         string strURL="";

         // create an instance of the web service
         AmazonSearchService ws = new AmazonSearchService( );

         if ( ws == null )
            return;

         // instantiate AsinRequest object -- willhold info
         // about the ISBN you want to find
         AsinRequest req = new AsinRequest( );
         req.asin = isbn;
         req.type = "heavy";
         req.tag = "webservices-20";      // replace with yours
         req.devtag = "XXXXXXXXXXXXX";      // replace with yours


         try
         {
            pi   = ws.AsinSearchRequest(req);
         }
         catch
         {
            MessageBox.Show(
               "Error accessing Amazon.com's web service.");
         }

         if ( pi.Details == null )
            return;

         // set the local variables based on the data you get back
         title = FixQuotes(pi.Details[0].ProductName);
         
         // if you have any authors, get the first	
         if ( pi.Details[0].Authors != null )
            author = FixQuotes(pi.Details[0].Authors[0]);

         publisher = FixQuotes(pi.Details[0].Manufacturer);
         pubDate = pi.Details[0].ReleaseDate;
         rank = GetRank(pi.Details[0].SalesRank);
         strURL = pi.Details[0].Url;

         // update the list box
         string results = title + " by " + author + ": " + 
            publisher + ", " + pubDate + ". Rank: " + rank;
         lbOutput.Items.Add(results);
         lbOutput.SelectedIndex = lbOutput.Items.Count -1;

         // update the database
         string commandString = @"Update BookInfo set isbn = '" +
            isbn + "', title = '" + title + "', publisher = '" +
            publisher + "', pubDate = '" + pubDate + "', rank = " +
            rank + ", link = '" + strURL + "', lastUpdate = '" + 
            System.DateTime.Now +  "', technology = '" +
            technology +  "', author = '" +
            author + "' where isbn = '" +
            isbn + "'";

         command.CommandText = commandString;
         try 
         {
            // if no rows were affected, this is a new record
            connection.Open( );
            int numRowsAffected = command.ExecuteNonQuery( );
            if (numRowsAffected == 0)
            {
               commandString = @"Insert into BookInfo values ('" +
                  isbn + "', '" + title + "', '" + publisher + "', '" +
                  pubDate + "', " + rank + ", '" + strURL + "', '" +
                  System.DateTime.Now + 
                  "', '" + technology + "', '" + author + "')";

               command.CommandText = commandString;
               command.ExecuteNonQuery( );
            }
         }
         catch (Exception e)
         {
            lbOutput.Items.Add("Unable to update database!");
            lbOutput.SelectedIndex = lbOutput.Items.Count -1;
         }
         finally
         {
            connection.Close( );      // clean up
         }
         Application.DoEvents( );      // update the UI
      }      // close for GetInfoFromISBN

      private int GetRank(string strRank)
      {
         if ( strRank == null )
            return 99999;
         string fixedString = strRank.Replace(",","");
         return Convert.ToInt32(fixedString);
      }


      private string FixQuotes(string s)
      {
         if (s == null)
            return null;
         string newString = s.Replace("'","");
         return newString;
      }


      private void timer1_Tick(object sender, System.EventArgs e)
      {
         
         if (timer1.Enabled)
            txtClock.Text = (--timeRemaining).ToString( ) + " seconds";
         else
            txtClock.Text = "Stopped";

         if ( timeRemaining < 1 )
         {
            timeRemaining = WaitTime;
            DataSet BookData = new DataSet( );
            BookData.ReadXml("aspnetIsbn.xml");
            foreach(DataRow Book in BookData.Tables[0].Rows)
            {
               string isbn = Book[0].ToString( );
               GetInfoFromISBN(isbn,"ASPNET");
            }

            BookData = new DataSet( );
            BookData.ReadXml("csharpIsbn.xml");
            foreach(DataRow Book in BookData.Tables[0].Rows)
            {
               string isbn = Book[0].ToString( );
               GetInfoFromISBN(isbn,"CSHARP");
            }

            BookData = new DataSet( );
            BookData.ReadXml("VBnetIsbn.xml");
            foreach(DataRow Book in BookData.Tables[0].Rows)
            {
               string isbn = Book[0].ToString( );
               GetInfoFromISBN(isbn,"VBNET");
            }
         }
      }

      private void btnNow_Click(object sender, System.EventArgs e)
      {
         timeRemaining = 2;
      }
   }
}

The key change is in GetInfoFromISBN. Once again you check that the ISBN is 10 characters:

if (isbn.Length != 10)
   return ;

But after that, the entire method is changed. The first thing to do is to declare an instance of ProductInfo. This object will represent all the information about the book that you'll retrieve from Amazon:

ProductInfo pi = null;

Next, you'll initialize local variables that will be used to hold the values returned within the ProductInfo object:

string title = "";
string author = "";
string publisher = "";
string pubDate = "";
int rank = 9999999;
string strURL="";

The first step is to create an instance of the AmazonSearchService.

AmazonSearchService ws = new AmazonSearchService( );

AmazonSearchService is declared within the Amazon.cs source file you created previously from the WSDL file.

The kind of search you'll be conducting for this version of the program is an Amazon Standard Item Number (which turns out, for books, to be the ISBN). To do this, you'll create an instance of an AsinRequest object, and set its asin property to the ISBN you're searching for:

AsinRequest req = new AsinRequest( );
req.asin = isbn;

You need to set the tag property to either the default (webservices-20) or to the name of your Amazon Associates account (if you run an Amazon Associates web site):

req.tag = "webservices-20";

You'll set the type to either "heavy" or "lite." Typically, a lite document contains only essential catalog information (product price, name, etc.), while a "heavy" document contains more complete product information.

Finally, you'll add the devtag that Amazon supplied you with when you registered the Web Services developer's kit:

req.type = "heavy";
req.devtag = "XXXXXXXXXXXXX";

You are now ready to call the AsinSearchRequest method on the web service, passing in the AsinRequest object you've created:

try
{
   pi   = ws.AsinSearchRequest(req);
}
catch
{
   MessageBox.Show(
      "Error accessing Amazon.com's web service.");
}

Assuming you have not thrown an exception, what you get back is a populated ProductInfo object, defined in Amazon.cs. Inside that object, among other things, is a Details collection, which has all the information you need.

Note that Amazon has declared that the Details property returns a collection of instances of the Details class.

You use the members of the Details collection to set the values of the local variables.

title = FixQuotes(pi.Details[0].ProductName);
if ( pi.Details[0].Authors != null )
...author = FixQuotes(pi.Details[0].Authors[0]);
publisher = FixQuotes(pi.Details[0].Manufacturer);
pubDate = pi.Details[0].ReleaseDate;
rank = GetRank(pi.Details[0].SalesRank);
strURL = pi.Details[0].Url;

The FixQuotes method is a private helper method that simply removes single quotes so as not to confuse SQL Server. You must check to make sure the Authors collection is not null, so that you do not throw an exception when you try to access the first member.

private string FixQuotes(string s)
{
   if (s == null)
      return null;
   string newString = s.Replace("'","");
   return newString;
}

We are taking only the first author listed (Authors[0]). More ambitious use of this field is left as an exercise to the reader who has too much time on his hands.

Note also that Amazon lists the publisher under the property Manufacturer, even though the Details object does have a Publisher property.

Now that you've populated the local variables, you can create the string for the listbox, and display that as you did previously,

string results = title + " by " + author + ": " + 
   publisher + ", " + pubDate + ". Rank: " + rank;
lbOutput.Items.Add(results);
lbOutput.SelectedIndex = lbOutput.Items.Count -1;

The rest of the updating code is identical to the previous version.

    [ Team LiB ] Previous Section Next Section