23.6 Invoking a Flickr Web Service Asynchronously with HttpClient

In this section, we present a Flickr Viewer app that allows you to search for photos on Flickr (flickr.com)—one of the first photo-sharing websites—then browse through the results. The app uses an asynchronous method to invoke a Flickr web service—that is, a software component that can receive method calls over the Internet using standard web technologies.

XML and LINQ to XML

Many web services return data in XML (Extensible Markup Language) format. XML is a widely supported standard for describing data that is commonly used to exchange that data between applications over the Internet. XML describes data in a way that both human beings and computers can understand.

The Flickr web-service method we use in this example returns XML by default. We’ll use LINQ to XML, which is built into the .NET platform, to process the data returned by Flickr. We’ll explain in Section 23.6.3 the small amount of XML and LINQ to XML needed—in the online XML and LINQ to XML chapter, we’ll explain these technologies in detail.

REST Web Service

Flickr provides a so-called REST web service that can receive method calls via standard web technologies. As you’ll see, the app invokes the a Flickr web-service method via a URL, just as you’d use to access a web page from a web browser. REST—which stands for Representational State Transfer—is discussed in our online REST Web Services chapter.

Asynchronously Invoking a Web Service

Because there can be unpredictably long delays while awaiting a web-service response, asynchronous Tasks are frequently used in GUI apps that invoke web services (or perform network communication in general) to ensure that the apps remain responsive to their users.

A Flickr API Key Is Required

To run this example on your computer, you must obtain your own Flickr API key at


https://www.flickr.com/services/apps/create/apply

and use it to replace the words YOUR API KEY HERE inside the quotes in line 18 of Fig. 23.4. This key is a unique string of characters and numbers that enables Flickr to track your usage of its APIs. Be sure to read the Flickr API’s Terms of Use carefully.

Flicker Viewer App

Our Flickr Viewer app (Fig. 23.4) allows you to search by tag for photos that users worldwide have uploaded to Flickr. Tagging—or labeling content—is part of the collaborative nature of social media. A tag is any user-supplied word or phrase that helps organize web content. Tagging items with meaningful words or phrases creates a strong identification of the content. Flickr uses the tags to improve its photo-search service, giving users better results.

Fig. 23.4 Invoking a web service asynchronously with class HttpClient.

Alternate View

   1   // Fig. 23.4: FickrViewerForm.cs
   2   // Invoking a web service asynchronously with class HttpClient
   3   using System;
   4   using System.Drawing;
   5   using System.IO;
   6   using System.Linq;
   7   using System.Net.Http;
   8   using System.Threading.Tasks;
   9   using System.Windows.Forms;
  10   using System.Xml.Linq;
  11
  12   namespace FlickrViewer
  13   {
  14      public partial class FickrViewerForm : Form
  15      {
  16         // Use your Flickr API key here--you can get one at:
  17         // https://www.flickr.com/services/apps/create/apply
  18         private const string KEY = "YOUR API KEY HERE";
  19
  20         // object used to invoke Flickr web service
  21         private static HttpClient flickrClient = new HttpClient();
  22
  23         Task<string> flickrTask = null; // Task<string> that queries Flickr
  24
  25         public FickrViewerForm()
  26         {
  27            InitializeComponent();
  28         }
  29
  30         // initiate asynchronous Flickr search query;
  31         // display results when query completes
  32         private async void searchButton_Click(object sender, EventArgs e)
  33         {
  34            // if flickrTask already running, prompt user
  35            if (flickrTask?.Status != TaskStatus.RanToCompletion)
  36            {
  37                var result = MessageBox.Show(
  38                   "Cancel the current Flickr search?",
  39                   "Are you sure?", MessageBoxButtons.YesNo,
  40                   MessageBoxIcon.Question);
  41
  42                // determine whether user wants to cancel prior search
  43                if (result == DialogResult.No)
  44                {
  45                   return;
  46                }
  47                else
  48                {
  49                   flickrClient.CancelPendingRequests(); // cancel search
  50                }
  51            }
  52
  53            // Flickr's web service URL for searches
  54            var flickrURL = "https://api.flickr.com/services/rest/?method=" +
  55               $"flickr.photos.search&api_key={KEY}&" +
  56               $"tags={inputTextBox.Text.Replace(" ", ",")}" +
  57               "&tag_mode=all&per_page= 500&privacy_filter=1";
  58
  59            imagesListBox.DataSource = null; // remove prior data source
  60            imagesListBox.Items.Clear(); // clear imagesListBox
  61            pictureBox.Image = null; // clear pictureBox
  62            imagesListBox.Items.Add("Loading..."); // display Loading...
  63
  64            // invoke Flickr web service to search Flickr with user's tags
  65            flickrTask = flickrClient.GetStringAsync(flickrURL);
  66
  67            // await flickrTask then parse results with XDocument and LINQ
  68            XDocument flickrXML = XDocument.Parse(await flickrTask);
  69
  70            // gather information on all photos
  71            var flickrPhotos =                                   
  72               from photo in flickrXML.Descendants("photo")      
  73               let id = photo.Attribute("id").Value              
  74               let title = photo.Attribute("title").Value        
  75               let secret = photo.Attribute("secret").Value      
  76               let server = photo.Attribute("server").Value      
  77               let farm = photo.Attribute("farm").Value          
  78               select new FlickrResult                           
  79               {                                                 
  80                  Title = title,                                 
  81                  URL = $"https://farm{farm}.staticflickr.com/" +
  82                     $"{server}/{id}_{secret}.jpg"               
  83            };                                                   
  84            imagesListBox.Items.Clear(); // clear imagesListBox
  85
  86            // set ListBox properties only if results were found
  87            if (flickrPhotos.Any())
  88            {
  89               imagesListBox.DataSource = flickrPhotos.ToList();
  90               imagesListBox.DisplayMember = "Title";
  91            }
  92            else // no matches were found
  93            {
  94               imagesListBox.Items.Add("No matches");
  95            }
  96         }
  97
  98         // display selected image
  99         private async void imagesListBox_SelectedIndexChanged(
 100           object sender, EventArgs e)
 101        {
 102           if (imagesListBox.SelectedItem != null)
 103           {
 104               string selectedURL =
 105                  ((FlickrResult) imagesListBox.SelectedItem).URL;
 106
 107               // use HttpClient to get selected image's bytes asynchronously
 108               byte[] imageBytes =                                  
 109                  await flickrClient.GetByteArrayAsync(selectedURL);
 110
 111               // display downloaded image in pictureBox
 112               MemoryStream memoryStream = new MemoryStream(imageBytes);
 113               pictureBox.Image = Image.FromStream(memoryStream);
 114           }
 115        }
 116      }
 117   }

As shown in the Fig. 23.4, you can type one or more tags (e.g., “pdeitel flowers”) into the TextBox. When you click the Search Button, the application invokes the Flickr web service that searches for photos, which returns an XML document containing links to the first 500 (or fewer if there are not 500) results that match the tags you specify. We use LINQ to XML to parse the results and display a list of photo titles in a ListBox. When you select an image’s title in the ListBox, the app uses another asynchronous Task to download the full-size image from Flickr and display it in a PictureBox.

23.6.1 Using Class HttpClient to Invoke a Web Service

This app uses class HttpClient (namespace System.Net.Http) to interact with Flickr’s web service and retrieve photos that match the search tags. Line 21 creates the static HttpClient object flickrClient that’s used in this app to download data from Flickr. Class HttpClient is one of many .NET classes that support asynchronous programming with async and await. In searchButton_Click (lines 32–96), we use class HttpClient’s GetStringAsync method to start a new Task (line 65). When we create that Task, we assign it to instance variable flickrTask (declared in line 23) so that we can test whether the Task is still executing when the user initiates a new search.

Software Engineering Observation 23.2

An HttpClient object is typically declared static so it can be used by all of an app’s threads. According to the HttpClient documentation, a static HttpClient object can be used from multiple threads of execution.

23.6.2 Invoking the Flickr Web Service’s flickr.photos.search Method

Method searchButton_Click (lines 32–96) initiates the asynchronous Flickr search, so it’s declared as an async method. First, lines 35–51 check whether you started a search previously and, if so, whether that search has already completed (lines 34–35). If an existing search is still being performed, we display a dialog asking if you wish to cancel the search (lines 37–40). If you click No, the event handler simply returns. Otherwise, we call the HttpClient’s CancelPendingRequests method to terminate the search (line 49).

Lines 54–57 create the URL that invokes Flickr’s flickr.photos.search web-service method, which searches for photos, based on the provided parameters. You can learn more about this method’s parameters and the format of the URL for invoking the method at


https://www.flickr.com/services/api/flickr.photos.search.html

In this example, we specify values for the following flickr.photos.search parameters:

  • api_key—Your Flickr API key. Remember that you must obtain your own key from https://www.flickr.com/services/apps/create/apply.

  • tags—A comma-separated list of the tags for which to search. In our sample executions it was “pdeitel,flowers”. If the user separates the tags with spaces, the app replaces the spaces with commas.

  • tag_mode—We use the all mode to get results that match all the tags specified in the search. You also can use any to get results that match one or more of the tags.

  • per_page—The maximum number of results to return (up to 500). If this parameter is omitted, the default is 100.

  • privacy_filter1 indicates only publicly accessible photos should be returned.

Line 65 calls class HttpClient’s GetStringAsync method, which uses the URL specified as the string argument to request information from a web server. Because this URL represents a call to a web-service method, calling GetStringAsync will invoke the Flickr web service to perform the search. GetStringAsync returns a Task<string> representing a promise to eventually return a string containing the search results. Line 68 then awaits the Task’s result. At this point, if the Task is complete, method searchButton_Click’s execution continues at line 71; otherwise, program control returns to method searchButton_Click’s caller until the results are received. This allows the GUI thread of execution to handle other events, so the GUI remains responsive while the search is ongoing. Thus, you could decide to start a different search at any time (which cancels the original search in this app).

23.6.3 Processing the XML Response

When the Task completes, program control continues in method searchButton_Click at line 68 where the app begins processing the XML returned by the web service. A sample of the XML is shown in Fig. 23.5.

Fig. 23.5 Sample XML response from the Flickr APIs.

Alternate View

  1    <rsp stat="ok">
  2       <photos page="1" pages="1" perpage="500" total="5">
  3          <photo id="8708146820" owner="8832668@N04" secret="40fabab966"
  4             server="8130" farm="9" title="fuchsiaflowers" ispublic="1"
  5             isfriend="0" isfamily="0"/>
  6          <photo id="8707026559" owner="8832668@N04" secret="97be93bb05"
  7             server="8115" farm="9" title="redflowers" ispublic="1"
  8             isfriend="0" isfamily="0"/>
  9          <photo id="8707023603" owner="8832668@N04" secret="54db053efd"
 10             server="8263" farm="9" title="yellowflowers" ispublic="1"
 11             isfriend="0" isfamily="0"/>
 12       </photos>
 13    </rsp>

XML Elements and Attributes

XML represents data as elements, attributes and text. XML delimits elements with start tags and end tags. A start tag consists of the element name, possibly followed by attribute-Name=value pairs, all enclosed in angle brackets. For example, line 1 in the sample XML


<rsp stat="ok">

is the start tag for an rsp element containing the entire web-service response. This tag also contains the attribute stat (for “status”)—the value "ok" indicates that the Flickr web-service request was successful. An end tag consists of the element name preceded by a forward slash (/) in angle brackets (for example, </rsp> in line 19, which denotes “end of response”).

An element’s start and end tags enclose

  • text that represents a piece of data or

  • other nested elements—for example, the rsp element contains one photos element (lines 2–12) and the photos element contains five photo elements (lines 3–11) representing the photos that were found by the web service.

An element is empty if it does not contain text or nested elements between its start and end tags. Such an element can be represented by a start tag that ends with />. For example, lines 3–5 define an empty photo element with several attribute=value pairs in the start tag.

Class XDocument and LINQ to XML

Namespace System.Xml.Linq contains the classes used to manipulate XML using LINQ to XML—we use several of these classes to process the Flickr response. Once the app receives the XML search results, line 68 (Fig. 23.4) uses XDocument method Parse to convert into an XDocument object the string of XML returned by the await expression. LINQ to XML can query an XDocument to extract data from the XML.

Lines 71–83 use LINQ to XML to gather from each photo element in the XDocument the attributes required to locate the corresponding photo on Flickr:

  • XDocument method Descendants (line 72) returns a list of XElement objects representing the elements with the name specified as an argument—in this case, the photo elements.

  • Lines 73–77 use XElement method Attribute to extract XAttributes representing the element’s id, title, secret, server and farm attributes from the current photo XElement.

  • XAttribute property Value (lines 73–77) returns the value of a given attribute.

For each photo, we create an object of class FlickrResult (located in this project’s FlickrResult.cs file) containing:

  • A Title property—initialized with the photo element’s title attribute and used to display the photo’s title in the app’s ListBox.

  • A URL property—assembled from the photo element’s id, secret, server and farm (a farm is a collection of servers on the Internet) attributes. The format of the URL for each image is specified at


http://www.flickr.com/services/api/misc.urls.html

We use a FlickrResult’s URL in imagesListBox_SelectedIndexChanged (Section 23.6.5) to download the corresponding photo when the user selects it in the ListBox.

23.6.4 Binding the Photo Titles to the ListBox

If there are any results (line 87), lines 89–90 bind the results’ titles to the ListBox. You cannot bind a LINQ query’s result directly to a ListBox, so line 89 invokes LINQ method ToList on the flickrPhotos LINQ query to convert it to a List first, then assigns the result to the ListBox’s DataSource property. This indicates that the List’s data should be used to populate the ListBox’s Items collection. The List contains FlickrResult objects, so line 90 sets the ListBox’s DisplayMember property to indicate that each FlickrResult’s Title property should be displayed in the ListBox.

23.6.5 Asynchronously Downloading an Image’s Bytes

Method imagesListBox_SelectedIndexChanged (lines 99–115) is declared async because it awaits an asynchronous download of a photo. Lines 104–105 get the URL property of the selected ListBox item. Then lines 108–109 invoke HttpClient’s GetByteArrayAsync method, which gets a byte array containing the photo. The method uses the URL specified as the method’s string argument to request the photo from Flickr and returns a Task<byte[]>—a promise to return a byte[] once the task completes execution. The event handler then awaits the result. When the Task completes, the await expression returns the byte[]. Line 112 creates a MemoryStream from the byte[] (which allows reading bytes as a stream from an array in memory), then line 113 uses the Image class’s static FromStream method to create an Image from the byte array and assign it to the Picture-Box’s Image property to display the selected photo.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset