XAM150 REST-based Web Services

Exercise 2: Communicate with a book service

In this exercise you will extend a provided Xamarin.Forms application to work with an existing REST service which manages a library of books. You will authenticate with the service, retrieve existing books, add new books, update books and delete books from your bookshelf.

Completed Screenshot
To complete the exercise, you will need Visual Studio for Windows or macOS with the Xamarin development tools installed. You will also need either an emulator/simulator or a device to run the exercise on. Please see the setup page if you need help installing the Xamarin development environment.

Explore the REST service

This exercise uses an existing REST service. This part covers the capabilities of the service; there is no code to write here.

The Book service is located at http://xam150.azurewebsites.net/api/books. This URL alone will return an HTTP 401 error. Here are the valid operations you can perform with this API using an HTTP request tool:

[
    {
        "isbn":"1025801012",
        "title":"Answers to Absurd Hypothetical Questions",
        "authors":["Randall Munroe"],
        "publishDate":"2014-09-04T00:00:00+00:00",
        "genre":"Nonfiction"
    }
]

Open the starter solution

This exercise utilizes a provided solution. Open the Exercise 2 > Start solution from your copy of the cloned or downloaded course materials in either Visual Studio for Windows or Visual Studio for Mac.

Examine the project structure. It has several projects in the solution.

The BookClient project is the PCL which contains all the code to create and process the UI for the application. You will do all of your work in this project. There are two Page classes in the project, one for the main view which shows a ListView for the books, and a second to add or edit a specific book.

The remaining projects are platform-specific. You will not need to modify any code in these projects; however, you will need to add packages to them and select one as your Startup Project for testing.


Add the NuGet packages

Here you will add the NuGet packages you will need and to make sure the app builds correctly.

  1. Add the Microsoft HTTP Client Libraries, Microsoft.Net.Http, NuGet package to the BookClient PCL project. It is not necessary to install it in the platform-specific projects.

To add this NuGet package via the Package Manager Console, found in the menu under Tools > NuGet Package Manager > Package Manager Console, run the following command:

Install-Package Microsoft.Net.Http

  1. Add the Json.NET NuGet package to the BookClient project and to any platform projects you intend to test with (iOS, Android and/or Windows). If you are using Visual Studio on Windows, you can do this at the solution level by right-clicking on the solution node and selecting Manage NuGet Packages for Solution. On Visual Studio for Mac, you will need to add the packages individually to each project through Add > Add Packages....

To add this NuGet package via the Package Manager Console, found in the menu under Tools > NuGet Package Manager > Package Manager Console, run the following command:

Install-Package Newtonsoft.Json

  1. You should be able to build the application, but do not attempt to run it yet as it has some unimplemented methods which throw exceptions.
  2. If your project builds correctly then you are done with this part. If not, you could be encountering a bug. When you add Microsoft HTTP Client Libraries to your projects, NuGet will also add dependencies to a few other packages to provide compile-time dependencies against core BCL classes. There is a known problem with certain versions of the NuGet client which causes a build error in the Xamarin.iOS and Xamarin.Android projects when they have a reference to these build packages. The error message will say something similar to:
warning MSB3277: Found conflicts between different versions of the same dependent assembly that could not be resolved. These reference conflicts are listed in the build log when log verbosity is set to detailed.

To resolve this issue, remove the following references from the Xamarin.iOS and Xamarin.Android platform-specific project - leave the package, just delete the references from the References folder. You should be able to build at that point. * System.Runtime * System.IO * System.Threading.Tasks

For more information on this error, see this MSDN blog post.


Examine the Book and BookManager classes

The service works with a set of books described in JSON. We've added a JSON parser (Json.NET), and have some code in the project for the object representation of the book.

  1. Expand the Data folder. You will find two files inside.
Source File Description
Book.cs This is the object representation for a single book. The definition should match the JSON description above. It has public properties defined for each of the passed fields. Json.NET is smart enough to manage camel-casing vs. pascal-casing so you can name the properties with standard C# conventions, the key thing is that the names are spelled correctly.
BookManager.cs This is the manager class which wraps the web service. It has stubbed out methods (with NotImplementedException code) for each of the operations you need to implement. The UI already has code in place to call each of these methods.

Login to the service

The service requires you "login" first to get a token. There is no user authentication (i.e. you do not have to enter any credentials); however, you must call a specific endpoint first to get a token. You must then send the token back to the server on each subsequent request in the HTTP header.

  1. In the Data folder, open BookManager.cs.
  2. Add the following field to your code. This is the base address of the service.
const string Url = "http://xam150.azurewebsites.net/api/books/";
  1. Add the following field to hold the token.
private string authorizationKey;
  1. Create a private method named GetClient which returns a Task<HttpClient>.
private async Task<HttpClient> GetClient()
{
   ...
}
  1. In the method, create a new HttpClient.
  2. If this is the first time the method has been called, then the authorizationKey field will not be set. In this case, you need to use GetStringAsync with the base URL + login to get the token.
  3. The returned token will have quotes around it which need to be removed. An easy way to do this is to use JsonConvert.DeserializeObject<string>(...). Save the result into the authorizationKey field.
  4. Add an Authorization header to the DefaultRequestHeaders collection of the HttpClient. Use the token as the value.
  5. Add an Accept header to the DefaultRequestHeaders collection of the HttpClient. Use application/json as the value.
  6. Return the HttpClient object from the method.
private async Task<HttpClient> GetClient()
{
    HttpClient client = new HttpClient();
    if (string.IsNullOrEmpty(authorizationKey))
    {
        authorizationKey = await client.GetStringAsync(Url + "login");
        authorizationKey = JsonConvert.DeserializeObject<string>(authorizationKey);
    }

    client.DefaultRequestHeaders.Add("Authorization", authorizationKey);
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    return client;
}

Perform a GET operation

Now that we can authenticate to the service, let's add our code to retrieve the books from the base Url.

  1. In the BookManager.cs file, find the method named GetAll which returns a Task<IEnumerable<Book>>.
  2. Use your GetClient method to retrieve an HttpClient to work with.
  3. Use GetStringAsync on the base Url to retrieve the array of books. You can use the C# async / await feature to make this easy to consume - make sure you add the async keyword to the method definition.
  4. Pass the returned string into JsonConvert.DeserializeObject to turn the JSON data into an object graph and return it back to the caller.
public class BookManager
{
    ...
    public async Task<IEnumerable<Book>> GetAll()
    {
        HttpClient client = await GetClient();
        string result = await client.GetStringAsync(Url);
        return JsonConvert.DeserializeObject<IEnumerable<Book>>(result);
    }
}
  1. Run the application and press the Refresh icon in the toolbar (at the top or bottom depending on the platform, but the icon looks like a pair of arrows in a circle).
  2. After a few seconds, the app should load a set of existing books and display them. Debug through the application if you do not see the data get loaded. Verify it is properly calling Login and then passing the authorization token into the GET call to retrieve the books.

Perform a POST (add) operation

We are now displaying the books, let's add support to add a new book using POST.

  1. Open the BookManager code and locate the method named Add which takes a title, author and genre and returns a Task<Book>.
  2. In the method, create a new Book object and populate the fields with the passed data:

    • Set the ISBN field to an empty string.
    • Make sure to create a new List to hold the passed author (we only allow a single author here for simplicity, but you could expand this code if you like to allow for multiples using a comma separator or some other convention).
    • Set the PublishDate to DateTime.Now.
  3. Get a client from your GetClient method.
  4. Use the PostAsync method against the base URL to add the book.

    • You will need to turn the Book object into JSON using JsonConvert.SerializeObject.
    • Create the HttpContent from the JSON string by creating a new StringContent object, use the constructor which also takes an encoding and media type.
    • The encoding should be Encoding.UTF8 and the media type should be application/json.
  5. The response from POST will be a JSON string represent the returned book. Deserialize it to a Book object and return the Book instance from the method.
public async Task<Book> Add(string title, string author, string genre)
{
    Book book = new Book() {
        Title = title,
        Authors = new List<string>(new[] { author }),
        ISBN = string.Empty,
        Genre = genre,
        PublishDate = DateTime.Now.Date,
    };

    HttpClient client = await GetClient();
    var response = await client.PostAsync(Url, 
        new StringContent(
            JsonConvert.SerializeObject(book), 
            Encoding.UTF8, "application/json"));

    return JsonConvert.DeserializeObject<Book>(
        await response.Content.ReadAsStringAsync());
}
  1. Build and run the application. Press the Add button ([+]). When you press the add button, it will add the book and return to the main screen.

Perform a PUT (update) operation

We are now able to display and add new books. Here you add the code necessary to update an existing book.

  1. Open the BookManager class and locate the method named Update which takes a Book and returns a Task.
  2. In the method implementation, get a new client and use the PutAsync method to send a JSON-encoded book to the base URL with the ISBN added to it.

    • So, if the ISBN is 12345678, then the URL would be api/books/12345678.
  3. You can use the same code to create the message body that you did when adding a book - but in this case we know where to put the book since it already has an assigned ISBN. Make sure to set the encoding and content-type as you did before.
  4. In this case, we don't have an explicit return type - but we want to return Task so that exceptions are properly returned back to the caller, and so the caller can use the await keyword to pause the progress of the method until the asynchronous method is finished.
  5. Try out the logic by running the application and tapping on an existing book to edit it.
public async Task Update(Book book)
{
    HttpClient client = await GetClient();
    await client.PutAsync(Url + book.ISBN,
        new StringContent(
            JsonConvert.SerializeObject(book),
            Encoding.UTF8, "application/json"));
}

Perform a DELETE operation

The final operation (and easiest) we have is DELETE.

  1. Open the BookManager class and locate the Delete method which takes a string ISBN and returns a Task.
  2. In the method body, get a new client and use the DeleteAsync method against the base URL + the ISBN (just like the UPDATE case).
  3. Run the application to test the delete logic - you can get to the functionality by swiping to the left on iOS, or by performing a long-click on Android and Windows (press + hold).
public async Task Delete(string isbn)
{
    HttpClient client = await GetClient();
    await client.DeleteAsync(Url + isbn);
}

Exercise summary

You have successfully utilized a REST service within your Xamarin application. As a fun side exercise, try adding an ActivityIndicator to the UI while you are loading the books. The completed solution has this code implemented through a Binding to the Page.IsBusy property if you'd like to just examine one way to accomplish this.

You can view the completed solution in the Exercise 2 > Completed folder of your copy of the cloned or downloaded course materials.

Go Back