Accessing REST Services


Many REST services are available on the Internet. Many of them are read-only services, offering only GET requests and providing some information. Just a few of the most useful are listed in the following table. See the list in the Resources section at the end of this chapter for more.

Open table as spreadsheet

Service

Description

Yahoo Geocode

Determines the latitude and longitude for a given address (works best with US addresses).

Amazon Product Search

Enables searching Amazon's product catalogues and purchasing products.

Amazon Open Search

Enables searching a number of search engines simultaneously.

eBay Product Search

Enables searching through eBay's product catalog.

Flickr Photo Search

Enables searching through the photographs posted to Flickr by photographer, topic, or other criteria.

Accessing REST Service Examples

One of the easier services to call, but still a very useful one, is Yahoo's Geocoding service. This provides the longitude and latitude for a given address. This data can then be used to map the location with one of the mapping services. The Geocoding service is a just-enough REST API using command-line parameters to identify the location. In addition, the call requires a unique token identifying the application calling the service. The token helps Yahoo identify heavy users of the system. In addition, each IP address calling the service is limited to 50,000 calls per day.

The Geocode service is accessed by sending a GET request to http://www.api.local.yahoo.com/MapsService/V1/geocode with the following parameters.

Open table as spreadsheet

Parameter

Description

appid

(Required) The unique string used to identify each application using the service. Note that this parameter name is case-sensitive. For testing purposes, you can use either YahooDemo (used by the Yahoo samples themselves) or ProXml (registered for the samples in this book). However, your own applications should have unique application IDs. You can register them at http://www.api.search.yahoo.com/webservices/register_application.

street

(Optional) The street address you are searching for. Note: this should be URL-encoded. That is, spaces should be replaced with + characters, and high ASCII or characters such as <, /, > etc. should be replaced with their equivalent using ‘%##’ notation.

city

(Optional) The city for the location you are searching for. Should be URL-encoded, although this is really only necessary if the city name contains spaces or high ASCII characters.

state

(Optional) The US state (if applicable) you are searching for. Either the two letter abbreviation or full name (URL-encoded) will work.

zip

(Optional) The US ZIP code (if applicable) you are searching for. This can be in either 5-digit or 5-digit-4-digit format.

location

(Optional) A free form field of address information containing the URL-encoded and comma-delimited request. For example: location=1600+Pennsylvania+Avenue+NW,+Washington,+DC

The return from the call is a block of XML corresponding to the XML schema:

      <?xml version="1.0" encoding="utf-8"?>      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"        xmlns="urn:yahoo:maps" targetNamespace="urn:yahoo:maps"        elementFormDefault="qualified">        <xs:element name="ResultSet">          <xs:complexType>            <xs:sequence>              <xs:element name="Result" type="ResultType" minOccurs="0" maxOccurs="50"/>            </xs:sequence>          </xs:complexType>        </xs:element>        <xs:complexType name="ResultType">          <xs:sequence>            <xs:element name="Latitude" type="xs:decimal"/>            <xs:element name="Longitude" type="xs:decimal"/>            <xs:element name="Address" type="xs:string"/>            <xs:element name="City" type="xs:string"/>            <xs:element name="State" type="xs:string"/>            <xs:element name="Zip" type="xs:string"/>            <xs:element name="Country" type="xs:string"/>          </xs:sequence>          <xs:attribute name="precision" type="xs:string"/>          <xs:attribute name="warning" type="xs:string" use="optional"/>        </xs:complexType>      </xs:schema> 

For example, calling the service for the US Whitehouse:

      http://api.local.yahoo.com/MapsService/V1/geocode?appid=YahooDemo&street=1600+Penns      ylvania+Avenue+NW&city=Washington&state=DC 

The previous call returns the following XML:

      <?xml version="1.0" encoding="UTF-8"?>      <ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

      xmlns="urn:yahoo:maps" xsi:schemaLocation="urn:yahoo:maps      http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd">        <Result precision="address">          <Latitude>38.8987</Latitude>          <Longitude>-77.037223</Longitude>          <Address>1600 PENNSYLVANIA AVE NW</Address>          <City>WASHINGTON</City>          <State>DC</State>          <Zip>20502-0001</Zip>          <Country>US</Country>        </Result>      </ResultSet> 

Notice that the full, corrected address is returned along with the latitude and longitude. Therefore, this service can also be used for address correction.

Listing 22-1 shows a class designed to wrap the Geocode service. The class provides two methods for generating the geographic location based on an address. In a real system, this class would likely include either other overrides as well as other geographic or mapping functions. Note that this class uses the System.Web.HttpUtility class. Therefore, you will need to add a reference to the System.Web.dll.

Listing 22-1: Wrapping the Geocode service

image from book
      using System;      using System.Collections.Generic;      using System.Text;      using System.Net;      using System.Web;      using System.Xml;      namespace ProXml.Samples.Rest {          public class Mapping {              private const string BaseUrl =                "http://api.local.yahoo.com/MapsService/V1/geocode";              private const string AppId = "ProXML";  //replace with your own code              public GeographicLocation Geocode(string street,                string city,                string state,                string zip) {                  GeographicLocation result = null;                  UriBuilder uri = new UriBuilder(BaseUrl);                  StringBuilder q = new StringBuilder();                  q.AppendFormat("appid={0}&", AppId);                  if(0!=street.Length) {                     q.AppendFormat("street={0}&", HttpUtility.UrlEncode(street));                  }                  if (0!=city.Length) {                     q.AppendFormat("city={0}&", HttpUtility.UrlEncode(city));                  }                  if (0!=state.Length) {                      q.AppendFormat("state={0}&", HttpUtility.UrlEncode(state));                  }                  if (0!=zip.Length) {                      q.AppendFormat("zip={0}&", HttpUtility.UrlEncode(zip));                  }                  uri.Query = q.ToString(0, q.Length - 1);                  WebRequest req = WebRequest.Create(uri.ToString());                  WebResponse resp = req.GetResponse();                  result = ExtractLocation(resp.GetResponseStream());                  return result;              }              public GeographicLocation Geocode(string location) {                  GeographicLocation result = null;                  UriBuilder uri = new UriBuilder(BaseUrl);                  StringBuilder q = new StringBuilder();                  q.AppendFormat("location={0}", HttpUtility.UrlEncode(location));                  uri.Query = q.ToString();                  WebRequest req = WebRequest.Create(uri.ToString());                  WebResponse resp = req.GetResponse();                  result = ExtractLocation(resp.GetResponseStream());                  return result;              }              private GeographicLocation ExtractLocation(System.IO.Stream stream) {                  GeographicLocation result = new GeographicLocation();                  using (XmlReader r = XmlReader.Create(stream)) {                      while (r.Read()) {                          if (r.IsStartElement() && !r.IsEmptyElement) {                             switch (r.Name.ToLower()) {                                 case "latitude":                                     r.Read(); //skip to content                                     result.Latitude = Double.Parse(r.Value);                                     break;                                 case "longitude":                                     r.Read(); //skip to content                                     result.Longitude = Double.Parse(r.Value);                                     break;                                 default:                                     break;                             }                         }                     }                  }                  return result;              }          }      } 
image from book

The class contains two overloaded methods for retrieving the location of an address. One method takes a single string, location, whereas the other takes street, city, and so forth. Most of the code involved is creating the appropriate URL for the query and does not involve XML processing, with the exception of the ExtractLocation method.

The ExtractLocation method uses the .NET XmlReader class to process the returned XML. Alternatively, you could use SAX or other lightweight XML handling method. As the XML returned is a small amount, you could also use the DOM to extract the appropriate elements. However, because doing so requires extra processing (generating a DOM usually requires the underlying framework to create the document tree), this solution is not used here. In this routine, the XmlReader is created and the read loop initialized. Note that the XmlReader.Create method is only available with the .NET Framework 2.0. For earlier versions of .NET, you use the line:

      using(XmlTextReader r = new XmlTextReader(stream)) { 

The code retrieves only the latitude and longitude from the response XML; however, it should be easy enough to add support for the other elements as well. You should also extend the return type (GeographicLocation), as shown in Listing 22-2.

Listing 22-2: GeographicLocation class

image from book
      using System;      using System.Collections.Generic;      using System.Text;      namespace ProXml.Samples.Rest {          public class GeographicLocation {              private double _lat = 0.0;              private double _long = 0.0;              public double Latitude {                  get { return _lat; }                  set { _lat = value; }              }              public double Longitude {                  get { return _long; }                  set { _long = value; }              }          }      } 
image from book

After you have the class, you can use it in an application. In this case, I simply create a Windows Forms application to test its functionality (see Figure 22-1).

image from book
Figure 22-1

Listing 22-3 has the code for the Geocode test application.

Listing 22-3: Testing the Geocode service

image from book
      private void GeoCodeButton_Click(object sender, EventArgs e) {          ProXml.Samples.Rest.GeographicLocation result = new              ProXml.Samples.Rest.GeographicLocation();          ProXml.Samples.Rest.Mapping m =              new ProXml.Samples.Rest.Mapping();          //clear the results first          this.LatitudeField.Text = "";          this.LongitudeField.Text = "";          if (0 != this.LocationField.Text.Length) {              // use the location variant          } else {              // use the street/city/state variant              result = m.Geocode(this.StreetField.Text,                  this.CityField.Text,                  this.StateField.Text,                  this.ZipField.Text);              if (null != result) {                  this.LatitudeField.Text = result.Latitude.ToString();                  this.LongitudeField.Text = result.Longitude.ToString();              }          }      } 
image from book

Because all the XML processing is in the Mapping class itself, the code to call the function is quite simple.

A second service Yahoo provides requires slightly different handling for both input and output, and so it is worth showing. The Term Extraction service, part of Yahoo search services, returns the important words and phrases in a block of text. This can be useful for categorizing articles or blog posts. As the submitted text can easily be larger than the 2K limit on GET requests, the submission should be made via POST. In addition, the XML returned contains multiple result values, each containing one of the significant terms or phrases.

The Term Extraction service can be accessed via the REST interface at:

http://www.api.search.yahoo.com/ContentAnalysisService/V1/termExtraction

The following table shows the parameters available for Yahoo Term Extraction.

Open table as spreadsheet

Parameter

Description

appid

(Required) The unique string used to identify each application using the service. Note that this parameter name is case-sensitive. For testing purposes, you can use either YahooDemo (used by the Yahoo samples themselves) or ProXml (registered for the samples in this book). However, your own applications should have unique application IDs. You can register them at http://www.api.search.yahoo.com/webservices/register_application.

context

(Required) The block of text that the terms will be extracted from. This should be URL-encoded. If this text is larger than the 2K GET limit, you use a POST request to process it.

query

(Optional) A query to help identify the topic of the context. For example, a block of text may discuss a number of different topics, which would all be included in the extraction. For example, a search for ‘java’ would likely include topics involving coffee, Indonesia, and programming languages. If you only want one topic extracted, provide a query to limit the extraction to the desired topic.

output

(Optional) Currently one of XML or JSON, defaulting to XML. JSON (Javascript Object Notation) is a non-XML notation for data, consisting of the objects serialized to text that can be converted back to Javascript objects via the eval method. Because this is a non-XML format, that's the last time I'll mention it here.

callback

(Optional) Used only if the output is set to JSON. This is the name of a client- side Javascript method to call to process the returned JSON data.

For example, you could call the service using a short block of text as a GET request:

      http://api.search.yahoo.com/ContentAnalysisService/V1/termExtraction?appid=ProXML&c      ontext=The+Dunlin,+Calidris+alpina,+is+a+small+wader.+It+is+a+circumpolar+breeder+i      n+Arctic+or+subarctic+regions.&query=bird 

This returns the following XML containing the key words in the sentence:

      <ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns="urn:yahoo:cate"        xsi:schemaLocation="urn:yahoo:cate      http://api.search.yahoo.com/ContentAnalysisService/V1/TermExtractionResponse.xsd">        <Result>subarctic regions</Result>        <Result>wader</Result>        <Result>alpina</Result>        <Result>regions</Result>      </ResultSet> 

Listing 22-4 shows a class designed to wrap this service.

Listing 22-4: Wrapping the Term Extraction service

image from book
      using System;      using System.Collections.Generic;      using System.Text;      using System.Net;      using System.Web;      using System.Xml;      namespace ProXml.Samples.Rest {          public class ContentAnalysis {              private const string BaseUrl =      "http://api.search.yahoo.com/ContentAnalysisService/V1/termExtraction";              private const string AppId = "ProXML"; //replace with your own AppID              public List<string> ExtractTerms(string context, string query) {                  List<string> result = null;                  UriBuilder uri = new UriBuilder(BaseUrl);                  StringBuilder q = new StringBuilder();                  q.AppendFormat("appid={0}&", AppId);                  q.AppendFormat("context={0}&", HttpUtility.UrlEncodeUnicode(context));                  if (0 != query.Length) {                     q.AppendFormat("query={0}&", HttpUtility.UrlEncode(query));                  }                  WebRequest req = WebRequest.Create(uri.ToString());                  //using Post as content may be longer than the 2K limit for GET                  req.Method = "POST";                  req.ContentType = "application/x-www-form-urlencoded";                  System.IO.Stream stream = req.GetRequestStream();                  Byte[] buffer = Encoding.UTF8.GetBytes(q.ToString(0,q.Length-1));                  stream.Write(buffer, 0, buffer.Length);                  stream.Close();                  WebResponse resp = req.GetResponse();                  result = BuildResponse(resp.GetResponseStream());                  return result;              }              private List<string> BuildResponse(System.IO.Stream stream) {                  List<string> result = new List<string>();                  using (XmlReader r = XmlReader.Create(stream)) {                     while (r.Read()) {                         if (r.IsStartElement("Result")) {                             result.Add(r.ReadElementContentAsString());                         }                     }                  }                  return result;              }          }      } 
image from book

The code is basically the same as that for the Geocode service with two major exceptions marked in the listing. First, because the request requires a POST, the Method property of the WebRequest is set to POST. In addition, the ContentType is set to application/x-www-form-urlencoded. This sets the Content-Type header on the request to ensure the Web server handles the query correctly. Finally, the query (containing the URL-encoded block of text) is written to the Request stream.

Because all the returned terms are wrapped in a Result element, parsing the resulting XML is easier. The IsStartElement has a version where you can include the element name you are searching for. After this is found, the resulting content can be retrieved with the ReadElementContentAsString method. Note that this method is only available with the .NET Framework 2.0. For version 1.1 and earlier, you should change the code for the if statement to:

      if (r.IsStartElement("Result") && !r.IsEmptyElement) {          r.Read(); //skips to the content          result.Add(r.Value);      } 

Just as with the Geocode service, you can create a simple test application (see Figure 22-2).

image from book
Figure 22-2

Listing 22-5 shows the code required to test the Extraction Wrapper class.

Listing 22-5: Testing the Te rm Extraction Wrapper class

image from book
      private void ExtractButton_Click(object sender, EventArgs e) {          ProXml.Samples.Rest.ContentAnalysis con =            new ProXml.Samples.Rest.ContentAnalysis();          List<string> result = con.ExtractTerms(this.ContextField.Text,              this.QueryField.Text);          this.TermList.DataSource = result;      } 
image from book

As a final example of calling existing REST services, Flickr provides a search service. This enables users to search the thousands of photographs that have been uploaded to Flickr. You can search by photographer, tag (topic), group (a named selection), or other criteria. The following table discusses the available parameters. A typical search takes the form:

      http://www.flickr.com/services/rest/?api_key={unique      key}&method=flickr.photos.search&tags=okapi 

Open table as spreadsheet

Parameter

Description

api_key

(Required) The api_key parameter is similar to the Yahoo appid parameter in that it is a unique key used to identify each application accessing the service. You can apply for your own key at: http://www.flickr.com/services/api/key.gne.

method

(Required) This identifies which of Flickr's methods you are calling (they all have the same URL). Flickr provides a number of methods beyond search. For more details, see the API documentation at: http://www.flickr.com/services/api/. For an example of what is possible to create using the APIs, see the Organizr application (http://www.flickr.com/tools/organizr.gne)

tags

(Optional) Tags are small blocks of text that can be attached to a photograph to identify its content, such as London, summer, or wedding. You can think of it as a category. When searching by tag, this parameter is a comma-delimited list of tags you want to search. By default, a photo containing any of these tags is returned. If you want to perform an AND search, see tag_mode below. (The sample code does not use this parameter).

tag_mode

(Optional) This controls whether the tag search is performed using an OR query (the default) or an AND query. Use the value any or omit it to do an OR query, or use all to select photos that have all the desired tags.

 

Many other parameters are supported, but not used by the sample application. See http://www.flickr.com/services/api/flickr.photos.search.html for the full list.

The XML returned from the service contains the ID information that can be used to create URLs to the photographs stored:

      <rsp stat="ok">        <photos page="1" pages="2" perpage="100" total="168">          <photo  owner="28255546@N00" secret="40a011bce6" server="16"      title="Okapi" ispublic="1" isfriend="1" isfamily="1"/>          <photo  owner="33394998@N00" secret="56e0d475dc" server="27"      title="Golgota" ispublic="1" isfriend="0" isfamily="0"/>          <photo  owner="33394998@N00" secret="1fc7d87431" server="30"      title="wenteltrap bouwen 2" ispublic="1" isfriend="0" isfamily="0"/>          <photo  owner="18081671@N00" secret="7b7ce51d35" server="42"      title="20060119 009" ispublic="1" isfriend="0" isfamily="0"/>          <photo  owner="18081671@N00" secret="fbad93dd15" server="15"      title="20060119 008" ispublic="1" isfriend="0" isfamily="0"/>          <photo  owner="18081671@N00" secret="dfbe7d90c8" server="39"      title="20060119 010" ispublic="1" isfriend="0" isfamily="0"/>          <photo  owner="18081671@N00" secret="e87de9ad47" server="33"      title="20060119 005" ispublic="1" isfriend="0" isfamily="0"/>          <photo  owner="18081671@N00" secret="dc776cf56c" server="43"      title="20060119 006" ispublic="1" isfriend="0" isfamily="0"/>          ...        </photos>      </rsp> 

To create the URL to the photo, you must first decide on the size of the photo you would like. The basic URL form is:

http://www.static.flickr.com/{server-id}/{id}_{secret}.jpg

In addition, you can add a suffix to select a different size, as outlined in the following table:

Open table as spreadsheet

Suffix

Image Size

none

Returns the image sized to 500 pixels along the longer axis. For example:

http://www.static.flickr.com/13/90283787_838c56eb46.jpg

m

Returns the image scaled to 240 pixels along the longer axis. For example:

http://www.static.flickr.com/29/89924158_adebcfd8b6_m.jpg

s

Returns the image scaled to a 75 pixel square. Note that this may cause some distortion in the original image because of scaling. For example:

http://www.static.flickr.com/34/90039890_d1113850b4_s.jpg

t

Returns a thumbnail version of the image, scaled to 100 pixels on the longest axis.

For example: http://www.static.flickr.com/29/52619028_e9541b248a_t.jpg

b

Returns a large version of the image, scaled to 1024 pixels along the longest axis. Note that this will only return an image if the original is larger than1024 pixels wide or high. For example:

http://www.static.flickr.com/27/67687221_107c1f3c06_b.jpg

o

Returns the original image. Note that the extension in this case may not actually be jpg, but will be the extension appropriate to whatever format the photograph was posted as. For example:

http://www.static.flickr.com/23/33000424_82bd503826_o.jpg

Listing 22-6 shows the code required to wrap the Flickr Photo Search service.

Listing 22-6: Wrapping the Flickr Photo Search service

image from book
      using System;      using System.Collections.Generic;      using System.Text;      using System.Net;      using System.Web;      using System.Xml;      using System.IO;      namespace ProXml.Samples.Rest {          public class Photos {              private const string BaseUrl = "http://www.flickr.com/services/rest/";              private const string AppId = "c0cbd699d50f296fa5b237eb4bdfbd1d"; //replace      with your own AppID              private const string FlickrPhotoSearch = "flickr.photos.search";              public PhotoInformation[] Search(string tags) {                  List<PhotoInformation> result = null;                  UriBuilder uri = new UriBuilder(BaseUrl);                  StringBuilder q = new StringBuilder();                  q.AppendFormat("api_key={0}&", AppId);                  q.AppendFormat("method={0}&", FlickrPhotoSearch);                  q.AppendFormat("tags={0}", HttpUtility.UrlEncodeUnicode(tags));                  uri.Query = q.ToString();                  WebRequest req = WebRequest.Create(uri.Uri);                  WebResponse resp = req.GetResponse();                  result = BuildPhotoList(resp.GetResponseStream());                  return result.ToArray();              }              private List<PhotoInformation> BuildPhotoList(System.IO.Stream input) {                  const string BasePhotoUrl = "http://static.flickr.com";                  UriBuilder ub = new UriBuilder(BasePhotoUrl);                  PhotoInformation pi = null;                  List<PhotoInformation> result = new List<PhotoInformation>();                  using (StreamReader read = new StreamReader(input)) {                     XmlReader r = XmlReader.Create(read);                     while (r.Read()) {                         if (r.IsStartElement("photo")) {                             pi = new PhotoInformation();                             pi.Title = r.GetAttribute("title");                             //build thumbnail URL                             ub.Path = String.Format("{0}/{1}_{2}_t.jpg",                                r.GetAttribute("server"),                                r.GetAttribute("id"),                                r.GetAttribute("secret"));                             pi.ThumbnailUrl = ub.ToString();                             //and photo URL                             ub.Path = String.Format("{0}/{1}_{2}.jpg",                                r.GetAttribute("server"),                                r.GetAttribute("id"),                                r.GetAttribute("secret"));                             pi.Url = ub.ToString();                             result.Add(pi);                         }                     }                  }                  return result;              }          }      } 
image from book

Just like the other services, the XML processing is isolated, in this case within the BuildPhotoList method. This method processes the returned XML, finding the photo elements. These contain the information needed to create the URLs to the graphics. The URLs are reconstructed in the PhotoInformation class (see Listing 22-7)

Listing 22-7: PhotoInformation class

image from book
      using System;      using System.Collections.Generic;      using System.Text;      namespace ProXml.Samples.Rest {          public class PhotoInformation {              private string _thumb;              private string _url;              private string _title;              public string ThumbnailUrl {                  get { return _thumb; }                  set { _thumb = value; }              }              public string Url {                  get { return _url; }                  set { _url = value; }              }              public string Title {                  get { return _title; }                  set { _title = value; }              }              public override string ToString() {                  if (String.Empty != _title) {                     return _title;                  } else {                     return _thumb;                  }              }          }      } 
image from book

After this class is created, searching and retrieving photos is quite easy (see Figure 23.3). Listing 22-8 shows the code that calls this class. Two methods are used: the first performs the initial search based on the keyword(s) provided. The second updates the image whenever a new item is selected in the list.

image from book
Figure 22-3

Listing 22-8: Using the Flickr Photo Search Wrapper class

image from book
      private void PhotoSearchButton_Click(object sender, EventArgs e) {          ProXml.Samples.Rest.Photos call =            new ProXml.Samples.Rest.Photos();          ProXml.Samples.Rest.PhotoInformation[] result =            call.Search(this.TagsField.Text);          PhotoList.DataSource = result;      }      private void PhotoList_SelectedIndexChanged(object sender, EventArgs e) {          PreviewImage.ImageLocation = "";          //get the selected item          ProXml.Samples.Rest.PhotoInformation pi = null;          ListBox lb = (ListBox)sender;          pi = (ProXml.Samples.Rest.PhotoInformation)lb.SelectedItem;          PreviewImage.ImageLocation = pi.Url;      } 
image from book

The code is for the Search button and the ListBox on the form. The Search button uses the wrapper class to retrieve the first block of photos returned (up to 500) and creates an array of PhotoInformation objects. This array is then bound to the ListBox. The ToString() method of the class is called to display the text value for each item in the list. This way it is a simple matter to retrieve the selected item from the ListBox to display the selected image.




Professional XML
Professional XML (Programmer to Programmer)
ISBN: 0471777773
EAN: 2147483647
Year: 2004
Pages: 215

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net