16.5 Searching By Category

Making a web service method call for each ISBN you want to check may not be the most efficient approach possible. Fortunately, Amazon has the ability to search by category. In the next (and final) iteration of this program, you'll dispense with your XML files with ISBNs and instead simply ask Amazon for all the books in the ASP.NET, C#, and VB.NET category.

Create a copy of the SalesRankDBWebServices project and name it SalesRankDBWebServices02, as shown in Example 16-6.

Example 16-6. SalesRankDBWebServices02
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;

namespace SalesRankDBWebService02
{
   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 System.Windows.Forms.Button btnNow;
      private System.Windows.Forms.TextBox txtClock;
      private System.Windows.Forms.ListBox lbOutput;
      private int timeRemaining;
      const int WaitTime =  900; // 15 min.

      public Form1( )
      {
         //
         // Required for Windows Form Designer support
         //
         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
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent( )
      {
         this.components = new System.ComponentModel.Container( );
         this.btnStart = new System.Windows.Forms.Button( );
         this.timer1 = new System.Windows.Forms.Timer(this.components);
         this.lbOutput = new System.Windows.Forms.ListBox( );
         this.btnNow = new System.Windows.Forms.Button( );
         this.txtClock = new System.Windows.Forms.TextBox( );
         this.SuspendLayout( );
         // 
         // btnStart
         // 
         this.btnStart.Location = new System.Drawing.Point(232, 336);
         this.btnStart.Name = "btnStart";
         this.btnStart.TabIndex = 0;
         this.btnStart.Text = "Start";
         this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
         // 
         // timer1
         // 
         this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
         // 
         // lbOutput
         // 
         this.lbOutput.Location = new System.Drawing.Point(8, 8);
         this.lbOutput.Name = "lbOutput";
         this.lbOutput.Size = new System.Drawing.Size(704, 303);
         this.lbOutput.TabIndex = 1;
         // 
         // btnNow
         // 
         this.btnNow.Location = new System.Drawing.Point(432, 336);
         this.btnNow.Name = "btnNow";
         this.btnNow.Size = new System.Drawing.Size(48, 23);
         this.btnNow.TabIndex = 7;
         this.btnNow.Text = "Now";
         this.btnNow.Click += new System.EventHandler(this.btnNow_Click);
         // 
         // txtClock
         // 
         this.txtClock.BackColor = System.Drawing.SystemColors.InactiveCaptionText;
         this.txtClock.Enabled = false;
         this.txtClock.Location = new System.Drawing.Point(320, 336);
         this.txtClock.Name = "txtClock";
         this.txtClock.Size = new System.Drawing.Size(88, 20);
         this.txtClock.TabIndex = 6;
         this.txtClock.Text = "";
         // 
         // Form1
         // 
         this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
         this.ClientSize = new System.Drawing.Size(728, 398);
         this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                                        this.btnNow,
                                                        this.txtClock,
                                                        this.lbOutput,
                                                        this.btnStart});
         this.Name = "Form1";
         this.Text = "Form1";
         this.Load += new System.EventHandler(this.Form1_Load);
         this.ResumeLayout(false);

      }
      #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
         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( );
      }


      // search by category rather than by ISBN
      // Note: continues to pass in technology so that the display
      // program does not have to change
      private void GetInfoFromAmazon(string keyword, string technology)
      {
         // create an instance of ProductInfo
         ProductInfo pi = null;

         // create local variables
         string title = "";
         string author = "";
         string publisher = "";
         string pubDate = "";
         int rank = 9999999;
         string strURL="";
         string isbn = "";


         // create instance of AmazonSearchService
         AmazonSearchService ws = new AmazonSearchService( );

         // Create instance of KeywordRequest for searching
         // by kewyord
         KeywordRequest req = new KeywordRequest( );

         // set the keyword to the parameter passed 
         // into the method
         req.keyword = keyword;
         req.type = "heavy";
         req.devtag = "xxxxxxxxxxxxx";
         req.mode = "books";
         req.tag = "webservices-20";      // replace with yours


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

         string strTotal = pi.TotalResults;
         int intPages = Convert.ToInt32(strTotal);
         int totalPages = intPages / 10;
         if ( intPages % 10 != 0 )
            totalPages ++;

         int currentPage = 1;

         while ( currentPage <= totalPages )
         {

            req.page = currentPage.ToString( );
            currentPage++;
            
            try
            {
               pi = ws.KeywordSearchRequest(req);
            }
            catch (Exception e)
            {
               MessageBox.Show(e.ToString( ) + " page: " + req.page +
                  " keyword: " + req.keyword + " total pages: " +
                  totalPages + " total found: " +
                  pi.TotalResults.ToString( ));
            }

            // Get back a colleciton of Details objects


            foreach ( Details d in pi.Details )
            {

               // extract info for this book
               isbn = d.Isbn;
               title = FixQuotes( d.ProductName);
               if ( d.Authors != null )
                  author = FixQuotes(d.Authors[0]);
               //publisher = FixQuotes(d.Publisher);
               publisher = FixQuotes(d.Manufacturer);
               pubDate = d.ReleaseDate;
               rank = GetRank(d.SalesRank);
               strURL = d.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 db
               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 
               {
                  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)
               {

                  MessageBox.Show(e.Message);
                  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 GetInfoFromAmazon


      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;
            GetInfoFromAmazon("ASP.NET","ASPNET");
            GetInfoFromAmazon("C#","CSHARP");
            GetInfoFromAmazon("VB.NET","VBNET");
         }
      }

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

   }
}

The GetInfoFromISBN method is removed, as is all the code to manipulate the XML files themselves (and, thankfully, so is the task of keeping those XML files up to date!).

GetInfoFromAmazon is not unlike the previous version of GetInfoFromISBN. However, this time, rather than creating an AsinRequest object, you'll create a KeywordRequest object:

private void GetInfoFromAmazon(string keyword, string technology)
{
   ProductInfo pi = null;
   string title = "";
   string author = "";
   string publisher = "";
   string pubDate = "";
   int rank = 9999999;
   string strURL="";
   string isbn = "";


   AmazonSearchService ws = new AmazonSearchService( );
   KeywordRequest req = new KeywordRequest( );
   req.keyword = keyword;
   req.type = "heavy";
   req.devtag = "xxxxxxxxxxxxx";
   req.mode = "books";
   req.tag = " webservices-20";

Notice that the keyword property has been assigned the keyword parameter passed in (i.e., C#, ASP.NET, or VB.NET).

Once the KeywordRequest object is created, you can invoke the KeywordSearchRequest method on the web service:

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

Assuming no exception is thrown, you once again have a fully populated ProductInformation object, and you are now free to iterate through the Details collection. The problem, however, is that the ProductInformation object returns pages of 10 items each. You can ask the ProductInformation object how many items there were in total, and you can use that number to calculate how many pages you must retrieve in total:

string strTotal = pi.TotalResults;
int intPages = Convert.ToInt32(strTotal);
int totalPages = intPages / 10;
if ( intPages % 10 != 0 )
   totalPages ++;

Once you know how many pages you need, you just keep incrementing the page property of the KeywordRequest object and then reinvoke KeywordSearchRequest:

while ( currentPage <= totalPages )
{

   req.page = currentPage.ToString( );
   currentPage++;
   
   try
   {
      pi = ws.KeywordSearchRequest(req);
   }

There will be one entry in Details for each book on that page that matches the search:

foreach ( Details d in pi.Details )
{
   isbn = d.Isbn;
   title = FixQuotes( d.ProductName);
   author = FixQuotes(d.Authors[0]);
   publisher = FixQuotes(d.Publisher);
   pubDate = d.ReleaseDate;
   rank = GetRank(d.SalesRank);
   strURL = d.Url;

The rest of the code is unchanged.



    Part I: The C# Language