advertisement

Print

Getting Started with the Safari Web Service

by Wei-Meng Lee
10/18/2004

Joining the ranks of Amazon.com and Google, the Safari Bookshelf has recently exposed a web service for developers to integrate its vast content of technical books into their web sites. Safari, a joint venture between O'Reilly Media Inc., and the Pearson Technology Group, is an online repository of thousands of technical books from leading publishers such as O'Reilly, Addison- Wesley, and Cisco Press. It is one of the more successful e-business models, where readers, for a subscription fee, can access a number of technical books online.

With its huge repository of online technical books, it is a logical step for Safari to expose a web service so that it can be integrated into related sites. For example, Microsoft could integrate the content of Safari into its MSDN site so that readers who are looking for sample code for a particular API could also find books related to that topic.

In this article, I will show you how to make use of the Safari web service using the .NET framework.

Understanding the Safari Web Service

If you are familiar with using web services in .NET, the first question you would ask before using the Safari web service is probably "So where is the WSDL document?" Unfortunately, Safari does not use SOAP as the web service's protocol, and hence there is no WSDL document for you to add web reference to in Visual Studio .NET. That means that you won't have the luxury of using the proxy class generated by Visual Studio .NET to access the web service.

Instead of SOAP, the Safari web service uses a URL-based Representational State Transfer (REST) structure. All of your requests are passed via the URL and the returning result is in XML format. To the developer, this means that you need to understand the structure of the query as well as the structure of the returning XML result. To communicate with the Safari web service, we use XML over HTTP (XMLHTTP).

The syntax for the query is pretty straightforward:

http://safari.oreilly.com/xmlapi/?token=insert token&search=term

where token is a string that identifies the user making the request (you can apply for one at the Safari affiliates site), and term is the search query.

Here is one sample query that searches for all books on XML:

http://safari.oreilly.com/xmlapi/?token=insert token&search=XML

Figure 1 shows the XML result returned by the Safari web service.

Safari Bookshelf

Study Shows Safari Saves Time -- A recent study by The Ridge Group of Princeton, New Jersey found that Safari Bookshelf delivers savings of about 24 times its cost. The group found that without the use of an Electronic Reference Library (ERL), the typical technology professional spends an average of 31 hours per month looking for answers, researching issues, and helping colleagues do the same. Safari subscribers, however, report an average of 13.5 hours saved per month--nearly half the amount of time lost by people who don't subscribe. Test it out: get a free trial.


Figure 1
Figure 1. The XML result returned by the Safari web service. Click image for full-size screen shot.

The returning result contains detailed information on books satisfying the search query. In particular, I have highlighted a few elements that I will be using for the example in this article.

  1. The root element of the response is <safari>. There are three attributes within this root element--book, section, and page. Depending on the search type, the returning result may contain one or more sections per book that satisfy the query. By default, each page displays a maximum of ten sections. This means that if there is more than one section returned in a book, the number of books displayed in a page will be less than ten. The page attribute specifies the total number of pages in the returning result.
  2. The <safari> root element contains repeating child elements called <book>. Therefore, to retrieve all of a book's information, you can use the //safari/book XPath expression.
  3. The <authorgroup> element contains child elements displaying author names.
  4. The <imagesmall>, <imagemedium>, and <imagelarge> elements contain URLs that point to images of the book cover.
  5. The <section> element contains sections of a book that are relevant to the search.

Once you understand the structure of this XML document, you can then extract the relevant information using XML techniques such as DOM, XPath, or XSLT.

Consuming the Safari Web Service using .NET

To illustrate how to consume Safari's web service, we will create an ASP.NET application using Visual Studio .NET 2003. First, populate the default Webform1.aspx as shown in Figure 2, with a TextBox and Button control.

Figure 2

Figure 2. Populating the default WebForm1.aspx

The bulk of the work will be done when the form is loaded. My application will consume the Safari web service and search for books on XML. The results from Safari will then be displayed on the page and users can view multiple pages returned by Safari. Listing 1 shows the complete source listing of this form. If you are the impatient type who wants to quickly see how it works, you can copy the listing and paste it into your own application. Figure 3 shows what the application will look like when it is run.

Figure 3
Figure 3. Running the application. Click image for full-size screen shot.

For the rest of you, I will walk you through the code. First, to use XMLHTTP in .NET, you can use the WebRequest and WebResponse objects from the System.Net namespace. As I will also be doing some XML processing when the Safari web service returns the results, I need to import the following namespaces:


Imports System.Net
Imports System.Xml

In the Form_Load event, I first declare a WebRequest object and a WebResponse object. I then formulate the search string required by Safari. I also retrieve the page number to display when users post back by clicking the Load button:


    Dim webReq As WebRequest
    Dim webResp As WebResponse
    Dim token As String = "Insert your own token here"

    Dim search As String = "&search=XML"
    Dim page As String = "&page=" & Request("txtpageno")

    Dim searchURL As String = _
     "http://safari.oreilly.com/xmlapi/?token=" & token & search & page

Next, invoke the search:


    '---invoke the search---
    webReq = WebRequest.Create(searchURL)
    webResp = webReq.GetResponse

The response from the web service is then read using the XmlTextReader class:


    '---read the response using the XmlTextReader---
    Dim reader As XmlTextReader
    reader = New XmlTextReader(webResp.GetResponseStream())

Load the response into an XmlDocument object so that you can manipulate the XML document to retrieve the relevant information:


    '---load the XML document using the XmlDocument object---
    Dim XMLDoc As New XmlDocument
    XMLDoc.Load(reader)

At this juncture, you can know the total number of pages your search results contain via the pages attribute in the <safari> root element:


    '---get the total number of pages for the result---
    Dim pages As Integer = _
        XMLDoc.SelectSingleNode("//@pages").InnerText

Since I want to list all of the book's information, I apply an XPath expression to the XML document so that I am only dealing with all the <book> elements:


    '---use XPath to retrieve all the books
    Dim xpath As String = "//safari/book"
    Dim nodes As XmlNodeList = XMLDoc.SelectNodes(xpath)

Display some header information at the top of the page:


    '---display header information---
    Response.Write("<b>Query:</b> " & searchURL & "<br />")
    Response.Write("<b>Total Pages</b> " & pages & "<br />")
    Response.Write("<b>Current Page</b> " & Request("txtpageno") & _
                   "<br />")

Lastly, navigate through all of the <book> elements and display the required information:


    '---display all the books information---
    Dim i As Integer
    For i = 0 To nodes.Count - 1
        '---display book cover---
        Response.Write("<img src='")
        Response.Write(nodes(i).SelectSingleNode_
                      ("imagemedium").InnerText())
        Response.Write("' />")

        '---display title of book---
        Response.Write("<b>" & _
           nodes(i).SelectSingleNode("title").InnerText() & _
           "</b><br/>")

        '---display all the authors---
        Dim authorNodes As XmlNodeList = _
            nodes(i).SelectNodes("authorgroup/author")
        Response.Write("<i>")
        For j As Integer = 0 To authorNodes.Count - 1
            Response.Write(authorNodes(j).SelectSingleNode_
                          ("firstname").InnerText & " ")
            Response.Write(authorNodes(j).SelectSingleNode_
                          ("lastname").InnerText)
            If (j < authorNodes.Count - 1) Then Response.Write(", ")
        Next
        Response.Write("</i><br/>")

        '---display the URL of the book in Safari---
        Response.Write("<a href='" & _
                       nodes(i).SelectSingleNode("url").InnerText() & _
                       "'>")
        Response.Write(nodes(i).SelectSingleNode("url").InnerText() & _
                       "</a><br/><br/>")

        '---display the relevant section of the book pertaining to 
        ' the search---
        Dim sectionNodes As XmlNodeList = _
            nodes(i).SelectNodes("section")
        Response.Write("<table border=1><tr><td>")
        For j As Integer = 0 To sectionNodes.Count - 1
            Response.Write(sectionNodes(j).SelectSingleNode_
                          ("heading").InnerText)
            Response.Write(sectionNodes(j).SelectSingleNode_
                          ("title").InnerText & "<br/>")

            '---display the url of the section---
            Response.Write("<a href='" & _
                sectionNodes(j).SelectSingleNode("url").InnerText & _
                "'>")
            Response.Write(sectionNodes(j).SelectSingleNode_
                ("url").InnerText & "</a><br/><br/>")
        Next
        Response.Write("</td></tr></table><br/>")
    Next

That's it! You can now test the application and view its output.

Besides the simple query I have used, you can also search by title:


'---look for titles element that contains the words "Web Services"---
Dim search As String = "&search=TITLE 'Web Services'"     

For a list of all of the available search syntax, refer to Safari's online help.

You can also list books grouped by category:


' displays books in a certain category
Dim search As String = "&search=CATEGORY=itbooks.dbase.oracle.prog" 

This document lists all of the categories used by Safari.

Searching for Books by Author

Before we end, let me show you one other example of using the Safari web service. Figure 4 shows WebForm2.aspx populated with the TextBox and Button controls.

Figure 4

Figure 4. Populating WebForm2.aspx

When the user specifies an author name and clicks Search, the application will query the Safari web service and display all of the books by that particular author. Figure 5 shows what this will look like. Each book is hyperlinked to its page in Safari.

Figure 5

Figure 5. Displaying books by a particular author

Listing 2 below shows the code for the Search button.

In this article, you have seen how to use the Safari web service using the .NET framework. Even though there isn't a SOAP implementation, with the sample code available on the newsgroup and Safari's site, it's not difficult to get started. Happy coding!

Listing 1.


Imports System.Net
Imports System.Xml

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As 
System.EventArgs) Handles MyBase.Load

    Dim webReq As WebRequest
    Dim webResp As WebResponse
    Dim token As String = "Insert your own token here"

    Dim search As String = "&search=XML"
    Dim page As String = "&page=" & Request("txtpageno")

    Dim searchURL As String = _
     "http://safari.oreilly.com/xmlapi/?token=" & token & search & page

    '---invoke the search---
    webReq = WebRequest.Create(searchURL)
    webResp = webReq.GetResponse

    '---read the response using the XmlTextReader---
    Dim reader As XmlTextReader
    reader = New XmlTextReader(webResp.GetResponseStream())

    '---load the XML document using the XmlDocument object---
    Dim XMLDoc As New XmlDocument
    XMLDoc.Load(reader)

    '---get the total number of pages for the result---
    Dim pages As Integer = _
        XMLDoc.SelectSingleNode("//@pages").InnerText

    '---use XPath to retrieve all the books
    Dim xpath As String = "//safari/book"
    Dim nodes As XmlNodeList = XMLDoc.SelectNodes(xpath)

    '---display header information---
    Response.Write("<b>Query:</b> " & searchURL & "<br/>")
    Response.Write("<b>Total Pages</b> " & pages & "<br/>")
    Response.Write("<b>Current Page</b> " & Request("txtpageno") & _
                   "<br/>")

    '---display all the books information---
    Dim i As Integer
    For i = 0 To nodes.Count - 1
        '---display book cover---
        Response.Write("<img src='")
        Response.Write(nodes(i).SelectSingleNode_
                      ("imagemedium").InnerText())
        Response.Write("' />")

        '---display title of book---
        Response.Write("<b>" & _
           nodes(i).SelectSingleNode("title").InnerText() & _
           "</b><br/>")

        '---display all the authors---
        Dim authorNodes As XmlNodeList = _
            nodes(i).SelectNodes("authorgroup/author")
        Response.Write("<i>")
        For j As Integer = 0 To authorNodes.Count - 1
            Response.Write(authorNodes(j).SelectSingleNode_
                          ("firstname").InnerText & " ")
            Response.Write(authorNodes(j).SelectSingleNode_
                          ("lastname").InnerText)
            If (j < authorNodes.Count - 1) Then Response.Write(", ")
        Next
        Response.Write("</i><br/>")

        '---display the URL of the book in Safari---
        Response.Write("<a href='" & _
                       nodes(i).SelectSingleNode("url").InnerText() & _
                       "'>")
        Response.Write(nodes(i).SelectSingleNode("url").InnerText() & _
                       "</a><br/><br/>")

        '---display the relevant section of the book pertaining to 
        ' the search---
        Dim sectionNodes As XmlNodeList = _
            nodes(i).SelectNodes("section")
        Response.Write("<table border=1><tr><td>")
        For j As Integer = 0 To sectionNodes.Count - 1
            Response.Write(sectionNodes(j).SelectSingleNode_
                          ("heading").InnerText)
            Response.Write(sectionNodes(j).SelectSingleNode_
                          ("title").InnerText & "<br/>")

            '---display the url of the section---
            Response.Write("<a href='" & _
                sectionNodes(j).SelectSingleNode("url").InnerText & _
                "'>")
            Response.Write(sectionNodes(j).SelectSingleNode_
                ("url").InnerText & "</a><br/><br/>")
        Next
        Response.Write("</td></tr></table><br/>")
    Next
End Sub
Listing 2.


Private Sub cmdSearch_Click(ByVal sender As System.Object, _
                            ByVal e As System.EventArgs) _
                            Handles cmdSearch.Click
    Dim webReq As WebRequest
    Dim webResp As WebResponse
    Dim token As String = "Insert your own token here"
    Dim search As String = "&search=AUTHOR=" & txtAuthor.Text & _
               "&sort=publishingDate&sortOrder=desc&view=book"
    Dim URL As String = "http://safari.oreilly.com/xmlapi/?token=" & _
                         token & search

    webReq = WebRequest.Create(URL)
    webResp = webReq.GetResponse

    Dim reader As XmlTextReader
    reader = New XmlTextReader(webResp.GetResponseStream())

    Dim XMLDoc As New XmlDocument
    XMLDoc.Load(reader)

    Dim xpath As String = "//safari/book"

    Dim nodes As XmlNodeList = XMLDoc.SelectNodes(xpath)

    Response.Write("Books by " & txtAuthor.Text & "<br/>")
    Dim i As Integer
    For i = 0 To nodes.Count - 1
        '---displays book cover and create a hyperlink---
        Response.Write("<a href='" & _
            nodes(i).SelectSingleNode("url").InnerText() & "'>")
        Response.Write("<img border='0' src='")
        Response.Write(nodes(i).SelectSingleNode_
                      ("imagemedium").InnerText())
        Response.Write("'/>")
        Response.Write("</a> ")
    Next
End Sub

Wei-Meng Lee (Microsoft MVP) http://weimenglee.blogspot.com is a technologist and founder of Developer Learning Solutions http://www.developerlearningsolutions.com, a technology company specializing in hands-on training on the latest Microsoft technologies.


Return to the O'Reilly Network