16.2. Creating the Web Services ClientIn an effort to make its information available to developers and "Amazon Associates" (sites that allow its users to purchase books through Amazon.com), Amazon has created a set of web services. For more information, see http://www.amazon.com/gp/aws/landing.html. Your application will use these web services, and you'll need to download the Amazon Web Services developer kit for Version 4 (as of this writing). The three things you'll need are an associatesTag (supplied by Amazon), a subscriberID (also supplied by Amazon), and the appropriate .wsdl file (publicly available through the Amazon Web Services pages). 16.2.1. Creating the Amazon proxyAs with all web services accessed through .NET, you must create a proxy class for 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 16.2.1.1 Creating the desktop applicationCreate a new desktop application named (for example) AmazonWebServiceClient, and be sure to copy the Amazon.cs file you created from the .wsdl file into that project's directory. Add the file to the project by right-clicking the project and choosing Add Existing Item. Amazon provides far more information about its books and products than we'll need, so we'll keep it simple and extract only a subset of the information it has available. In addition, it provides methods that return information about a collection of books, but for now we'll greatly simplify the process (while sacrificing performance) by looking up each book one by one. To do so you'll create a set of XML files to contain the ISBNs of the books you want to track. I've divided these by technology so that I have a CSharpISBN.xml file, a VBNETISBN.xml file, and an ASPNET_ISBN.xml file. Example 16-1 shows an excerpt from one of these files. Example 16-1. CSharpISBN.xml<isbns> <isbn>193183654X</isbn> <isbn>0130461334</isbn> <isbn>1893115593</isbn> <isbn>0130622214</isbn> <isbn>1861007043</isbn> <isbn>1861004982</isbn> <isbn>0672320711</isbn> <isbn>0596001819</isbn> <isbn>0735612897</isbn> <isbn>0735612900</isbn> <isbn>0596003099</isbn> <isbn>0596003765</isbn> <isbn>0072133295</isbn> <isbn>0672322358</isbn> <isbn>0072193794</isbn> <isbn>067232122X</isbn> <isbn>1588801926</isbn> <isbn>0672321521</isbn> <isbn>0735615683</isbn> <isbn>0201729555</isbn> </isbns>
As each ISBN is read, the relevant values (title, publisher, rank) are found on the Amazon web site and stored in the Database table. A simple listbox is then updated to indicate progress. Once all the books are recorded, the system becomes dormant while a timer ticks down the remaining time between sessions. You can force a new session by clicking the Now button. This UI was intentionally created to be as simple as possible. Example 16-2 is the complete desktop application, with analysis to follow. Example 16-2. SalesRankDBWebServices#region Using directives using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Windows.Forms; #endregion namespace AmazonWebServiceClient { partial class AmazonWebServiceClient : Form { private int timeRemaining; const int WaitTime = 900; // 15 minutes private string connectionString; private System.Data.SqlClient.SqlConnection connection; private System.Data.SqlClient.SqlCommand command; public AmazonWebServiceClient( ) { InitializeComponent( ); } private void AmazonWebServiceClient_Load( object sender, EventArgs e ) { // connection string to connect to the Sales Rank Database connectionString = "server=localhost;Trusted_Connection=true;database=AmazonSalesRanks"; // Create connection object, initialize with // connection string. connection = new System.Data.SqlClient.SqlConnection( connectionString ); // Create a SqlCommand object and assign the connection command = new System.Data.SqlClient.SqlCommand( ); command.Connection = connection; timeRemaining = 1; // when you first start up, get the info. UpdateButton( ); } private void btnStart_Click( object sender, EventArgs e ) { // toggle the timer updateTimer.Enabled = updateTimer.Enabled ? false : true; UpdateButton( ); } private void btnNow_Click( object sender, EventArgs e ) { timeRemaining = 2; } private void UpdateButton( ) { btnStart.Text = updateTimer.Enabled ? "Stop" : "Start"; } private void updateTimer_Tick( object sender, EventArgs e ) { if ( updateTimer.Enabled ) txtClock.Text = ( --timeRemaining ).ToString( ) + " seconds"; else txtClock.Text = "Stopped"; // hi ho, hi ho, it's off to work we go... if ( timeRemaining < 1 ) { timeRemaining = WaitTime; // reset the clock // create data set based on xml file DataSet BookData = new DataSet( ); BookData.ReadXml( "aspnet_isbn.xml" ); // iterate through, calling GetInfoFromISBN for // each isbn found in file 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 GetInfoFromISBN( string isbn, string technology ) { if ( isbn.Length != 10 ) return; AWSProductData productData = new AWSProductData( ); ItemLookup lookup = null; try { ItemLookupRequest req = new ItemLookupRequest( ); req.IdType = ItemLookupRequestIdType.ASIN; req.ItemId = new string[1]; req.ItemId[0] = isbn; // req.SearchIndex = "Books"; lookup = new ItemLookup( ); lookup.AssociateTag = "libertyassocia00A"; lookup.SubscriptionId = "0SD959SZV6KXV3BKE2R2"; lookup.Request = new ItemLookupRequest[1]; lookup.Request[0] = req; } catch ( System.Exception e ) { lblStatus.Text = e.Message; } ItemLookupResponse response; Items info; Item[] items; Item item; int salesRank = -1; string author = string.Empty; string pubDate = string.Empty; string publisher = string.Empty; string title = string.Empty; string strURL = string.Empty; try { response = productData.ItemLookup( lookup ); info = response.Items[0]; items = info.Item; item = items[0]; salesRank = item.SalesRank == null ? -1 : Convert.ToInt32(item.SalesRank); author = FixQuotes( item.ItemAttributes.Author[0] ); pubDate = FixQuotes(item.ItemAttributes.PublicationDate); publisher = FixQuotes(item.ItemAttributes.Publisher); title = FixQuotes(item.ItemAttributes.Title); strURL = item.DetailPageURL; } catch ( System.Exception ex) { lblStatus.Text = ex.Message; } // update the list box string results = title + " by " + author + ": " + publisher + ", " + pubDate + ". Rank: " + salesRank; 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 = " + salesRank + ", 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 + "', '" + FixQuotes( strURL ) + "', " + salesRank + ", '" + System.DateTime.Now + "', '" + technology + "', '" + author + "')"; command.CommandText = commandString; command.ExecuteNonQuery( ); } } catch ( Exception ex ) { lblStatus.Text = ex.Message; lbOutput.Items.Add( "Unable to update database!" ); lbOutput.SelectedIndex = lbOutput.Items.Count - 1; } finally { connection.Close( ); // clean up } } // close for GetInfoFromISBN private string FixQuotes( string s ) { if ( s == null ) return string.Empty; return s.Replace( "'", "''" ); } } // end class } // end name space The program declares a connection string, along with SQLConnection and SQLCommand objects which will be initialized when the form is loaded: private string connectionString; private System.Data.SqlClient.SqlConnection connection; private System.Data.SqlClient.SqlCommand command; You can set the Load event by clicking the form and switching from Properties to Events. Double-click the Load event and the skeleton for the load event handler is created for you. Within that event handler, you'll create your connection string (this example uses a trusted connection; you may need to provide a username and password depending on how your database is configured), and the connection and command objects are configured: private void AmazonWebServiceClient_Load( object sender, EventArgs e ) { connectionString = "server=localhost;Trusted_Connection=true;database=AmazonSalesRanks"; connection = new System.Data.SqlClient.SqlConnection( connectionString ); command = new System.Data.SqlClient.SqlCommand( ); command.Connection = connection; The member variable timeRemaining is initialized to one second, and the buttons are updated to set the text on the Start button: timeRemaining = 1; // when you first start up, get the info. UpdateButton(); } Each time the timer clicks, the updateTimer_Tick method is called. If the timer is enabled (the user has not clicked Stop), the timeRemaining member variable is decremented, and when it hits 0 it is time to process the books: if ( updateTimer.Enabled ) txtClock.Text = ( --timeRemaining ).ToString() + " seconds"; else txtClock.Text = "Stopped"; // hi ho, hi ho, it's off to work we go... if ( timeRemaining < 1 ) { The first step is to reset the timer to WaitTime (a constant equivalent to 15 minutes) and then to process the .xml files: timeRemaining = WaitTime; // reset the clock DataSet BookData = new DataSet(); try { BookData.ReadXml( "aspnet_isbn.xml" ); } This creates a dataset, in which each row represents an entry in the XML file. Once the books are read, you extract each ISBN in turn, and call the helper method GetInfoFromISBN, passing in the ISBN and the "technology" under which this ISBN will be stored in the database: foreach ( DataRow Book in BookData.Tables[0].Rows ) { string isbn = Book[0].ToString(); GetInfoFromISBN( isbn, "ASPNET" ); } GetInfoFromISBN is the heart of the program; it is here that you contact the Amazon Web Service. The first step is to ensure that the length of the ISBN is exactly 10 (a full check would use a regular expression to ensure that the ISBN is 9 integers followed by either an integer or the letter X, and then to perform a checksum on the ISBN [the final digit represents the checksum value], but that is left as an exercise for the reader). The Amazon.cs file defines a number of useful objects. The ones we'll use for this example include the AWSProductData , the ItemLookup and ItemLookupRequest, as well as the Item objects and collections. Here are the steps:
To make this work, you must first create an instance of ItemLookupRequest and set its IDType property to the enumerated type ItemLookupRequestIdType.ASIN: ItemLookupRequest req = new ItemLookupRequest(); req.IdType = ItemLookupRequestIdType.ASIN; Initialize its ItemID array to hold one string, and set that string to the ISBN you are looking for: req.ItemId = new string[1]; req.ItemId[0] = isbn; Next, instantiate an ItemLookup object, and set its AssociateTag and SubscriptionID properties: lookup.AssociateTag = "libertyassocia00A"; lookup.SubscriptionId = "Your ID Here"; Initialize its Request property to be an array of one object, and set that object to the ItemLookupRequest object you created earlier: lookup.Request = new ItemLookupRequest[1]; lookup.Request[0] = req;
You're ready to make your request. Do so in a try block to catch any exceptions that might be thrown in the process. Begin by invoking the ItemLookup method: response = productData.ItemLookup( lookup ); Response should now be non-null. You might add error checking to handle a null response from Amazon (left out here to simplify the code). The Item property returns an object of type Items, which is an array of Item objects. You will extract the first Item object, which will contain information about the book you've requested: info = response.Items[0]; items = info.Item; item = items[0]; You can now set local variables to hold the values you've retrieved. The FixQuotes method is a helper method to convert single quotes in any string you receive so that they will not cause problems for the database: salesRank = item.SalesRank == null ? -1 : Convert.ToInt32(item.SalesRank); author = FixQuotes( item.ItemAttributes.Author[0] ); pubDate = FixQuotes(item.ItemAttributes.PublicationDate); publisher = FixQuotes(item.ItemAttributes.Publisher); title = FixQuotes(item.ItemAttributes.Title); strURL = item.DetailPageURL; With this information in hand, you are ready to update the listbox and, more important, to update the database. When updating the database, you'll first try an Update statement. If the number of rows affected is 0, the row doesn't yet exist in the database, so you'll insert the values.
With that done, you're ready to move on to the next ISBN. |