GoodReads API

by CameronM 6. August 2014 13:57

GoodReads provides a way to discover books you might like based on your reading habits. You can create digital bookshelves containing books you have read or want to read. You can also search for books by title, which is the API function we’re going to tacking in today’s API August post.

Sigining up for a GoodReads API Key

Like the Google APIs, you need to sign up for an API key. Head to the api page for more information.

Creating our Visual Studio solution

Start by creating a new Class Library (Portable) project. Name the project GoodReads.PCL and the solution GoodReads.

Add a two new folders to the project, one named Models and the other ViewModels. Using nuget, either via the ‘Manage Packages’ context menu, or using the console, add the Microsoft HTTP Client Libraries.

Querying the GoodReads service

The Url for the GoodReads API is as follows:

http://www.goodreads.com/search/search?format=xml&key=[Your Key]&q=[Query] 

Unlike the Google Places API, GoodReads returns XML formatted results, so there are a couple of ways to create the C# classes to use for serialization. One option is to use the Paste XML as Class option from the Edit/Paste Special menu. Another is to use the xsd.exe tool, which is what I did when first investigating the GoodReads API some time ago.

The BookListViewModel contains an ObservableCollection of GoodreadsBook results and a single asynchronous call to the GoodReads web service.

public class BookListViewModel : NotifyBase
{
    string key = "[YourAPIKey]";
    string baseUrl = "http://www.goodreads.com/search/search?";
 
    public BookListViewModel()
    {
        this.Books = new ObservableCollection<GoodreadsBook>();
    }
    private ObservableCollection<GoodreadsBook> _books { getset; }
 
    public ObservableCollection<GoodreadsBook> Books
    {
        get { return _books; }
        set
        {
            _books = value;
            OnPropertyChanged("Books");
        }
    }
 
    public async Task LoadDataAsync(string Search)
    {
        try
        {
            Uri uri = new Uri(string.Format("{0}format=xml&key={1}&q={2}", baseUrl, key, Search));
 
            HttpClient client = new HttpClient();
            var response = await client.GetStringAsync(uri);
 
            StringReader sr = new StringReader(response);
 
            XmlSerializer xs = new XmlSerializer(typeof(GoodreadsResponse));
            GoodreadsResponse results = (GoodreadsResponse)xs.Deserialize(sr);
 
            // add each book from the works array
            for (int i = 0; i < results.Search.Results.Works.Length; i++)
            {
                this.Books.Add(results.Search.Results.Works[i].Books[0]);
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }
}

Creating the Windows Phone project

Add a new Windows Phone project to your solutions (it’s in the C#/Store Apps category in Visual Studio).

Add a reference to the GoodReads.PCL project so that we can use our ViewModel.

Create an application property that exposes our MainView model, so that it can be referenced throughout the app. In the App.xaml.cs file add the following code.

private static BookListViewModel _viewModel { getset; }
public static BookListViewModel ViewModel
{
    get
    {
        if (_viewModel == null)
            _viewModel = new BookListViewModel();
        return _viewModel;
    }
}

Calling the ViewModel from the MainPage

We’ll override the OnNavigatedTo event to link the MainPage with the BookListViewModel. This ensure that the ViewModel is available whenever the page gets called. In the MainPage.cs file add the following code.

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    this.DataContext = App.ViewModel;
    base.OnNavigatedTo(e);
}

We’re using a simple textbox to collect the value to add to the search query so that users can enter the name of a book to search for. We’ve wired up the OnKeyDown event so that when the user taps the ‘enter’ key on the keyboard, the search is started.

private async void OnKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Enter)
    {
        _searchTerm = SearchTextBox.Text.Trim();
 
        if (String.IsNullOrEmpty(_searchTerm))
            return;
        this.Focus();
 
        // search the API
        await App.ViewModel.LoadDataAsync(_searchTerm);
 
    }
}

Now that we’ve linked the ViewModel, it’s time to add the XAML that will display the results. As before, we're using our derived LongListSelector control, so add a new xmlns named local to your MainPage.xaml as follows:

xmlns:local="clr-namespace:GoodReads.WP"

Replace the LayoutRoot Grid with the following mark-up:

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
 
    <StackPanel Grid.Row="0" Margin="12,17,0,12" >
        <TextBox x:Name="SearchTextBox"
                    InputScope="Search" 
                    KeyDown="OnKeyDown"/>
    </StackPanel>
 
    <!--ContentPanel - place additional content here-->
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <local:LongListSelector x:Name="lstResults" 
                ItemsSource="{Binding Books}"  
                ItemTemplate="{StaticResource BookItemTemplate}" />
    </Grid>
</Grid>

One change you'll notice is that we're using a resource called BookItemTemplate rather than simply adding the markup to the ItemTemplate tag as we did in the Google Places API post. This maks it much cleaner to read the markup and also enables reuse of your resources, if you make them application wide. In this case, the resource has been added at the page level, rather than application, so you'll need to add the following to your page XAML.

<phone:PhoneApplicationPage.Resources>
    <DataTemplate x:Key="BookItemTemplate">
        <StackPanel Margin="12,12,0,12" Orientation="Horizontal">
            <Image Source="{Binding ThumbnailURL}"/>
            <StackPanel Margin="12,0,0,0">
                <TextBlock HorizontalAlignment="Left" 
                            Height="Auto" 
                            TextWrapping="Wrap" 
                            Text="{Binding Title}" 
                            VerticalAlignment="Top" 
                            Width="Auto" 
                            Style="{StaticResource PhoneTextAccentStyle}" 
                            Margin="0,0,0,0"/>
                <TextBlock HorizontalAlignment="Left" 
                            Height="Auto" 
                            TextWrapping="Wrap" 
                            Text="{Binding Author.Name}" 
                            VerticalAlignment="Top" 
                            Width="Auto" 
                            Margin="0,0,0,0"/>
            </StackPanel>
        </StackPanel>
    </DataTemplate>
</phone:PhoneApplicationPage.Resources>

Run the app on your device and perform a search for your favourite book. You should end up with a list of results that match your search term.


Tags: ,

C# | Windows Phone 8