Consuming SOAP-based Web Services

Duration

45 minutes

Goals

In this exercise, you will modify a basic Xamarin.Forms application shell to include support to hit an existing SOAP-based service and manipulate the returned data. The UI has already been created, you will be filling in the web service access code.

Exercise Overview

Try to complete the exercise by using the following high-level instructions, and going into the step-by-step instructions if you need some guidance. You will be using a starter solution located in Exercise 1. This solution has projects for Xamarin.iOS, Xamarin.Android, and Windows UWP.

Your goal is to implement the four operations (Create, Get, Update and Delete) operations in the BookManager class in the Data folder of the project. There are //TODO comment markers in each method. You will not need to make any other changes to the project.

The Book SOAP service is located at http://xam150.azurewebsites.net/services/books.svc. Normally, you would create a client proxy using the SLSvcUtil.exe tool from the Silverlight SDK, but this step has already been done for you and the proxy is already in the starter project.

How do I generate a WCF proxy from WSDL?

Here are the valid operations you can perform from the proxy:

  1. Call LoginAsync to get a token (if you don't pass this token to the other methods, you'll get an Unauthorized status returned). This will create a unique session for your application and will populate the session with a few default books. The first detail step below has a walk through for this step.
  2. All other calls need to add the string-based token (with no quotes) as the HTTP "Authorization" header. This is what identifies you to the service. This is a bit complicated to do, so follow the instructions described below.
  3. Call GetBooksAsync to get a list of the books in your session. This will return an array of book objects which look something like:
    public class Book
    {
        public string[] Authors { get; set; }
        public string Genre { get; set; }
        public string ISBN { get; set; }
        public System.DateTime PublishDate { get; set; }
        public string Title { get; set; }
    }
    
  4. Call UpdateBookAsync to update a book.
  5. Call DeleteBookAsync to delete a book, passing the ISBN code to identify the book.
  6. Call CreateBookAsync to add a new book, passing the book object as the parameter; note that the ISBN code must be empty and the response will include the book object with the ISBN key filled in.
  7. Test each of these operations using the pre-built UI.

Tip: If you are doing this exercise in a live class, make sure to take advantage of the instructor if you run into trouble or have any questions!

Steps

(Optional) Examine the existing starter solution

You will be modifying an existing Xamarin.Forms application, so as a first step, let's walk through the solution structure so you have a basic idea about the application.

  1. Open the starter project contained in the Part01 Resources folder and examine the project structure. It has four projects in the solution:
    Project Description
    BookClient This 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 Data folder has the two files you are primarily interested in.
    • BookManager is a class you will modify in order to perform each operation with the SOAP service.
    • Books.cs file is the generated proxy file which you can look at. This contains the above definition of the Book as well as the client proxy to access all the SOAP operations.
    BookClient.Droid This is the Android platform-specific head project.
    BookClient.iOS This is the iOS platform-specific head project.
    BookClient.UWP This is the Windows UWP platform-specific head project. You can use this project if you are on Windows 10 and have the UWP SDK.

Add support to login to the service

The service requires you login first and get an authentication token. For simplicity, we aren't using any credentials or certificates - but you must call a specific endpoint first and then take the result of that and return it on each subsequent request in the HTTP header.

  1. Open the BookManager class in the Data folder in the BookClient project. This is the PCL project which has all our shared code. This is where all our work will be performed.
  2. First, add a constant string to the class to hit the base URL. The value for the string should be "http://xam150.azurewebsites.net/services/books.svc".
  3. Since we will need to do this authentication once, but then add the token into the header for all other request, we will create a single method to create our BooksClient object to access the service.
  4. Create a new method in the class named GetClient which returns a BooksClient.
  5. In the method, create a new BooksClient instance. Since this is a mobile application which does not have an application configuration file, we will need to use a constructor that takes the binding and endpoint address.
    • Pass a new instance of a BasicHttpBinding class as the binding. This is the only binding available to Xamarin applications. You will need to add a using statement to System.ServiceModel.
    • Pass your URL constant wrapped in an EndpointAddress object as the endpoint.

Show Code

  1. Add a new private field of type string to hold the authorization token. The completed lab names this field authorizationKey.
  2. Add a new method named Login which returns a Task<bool>.

The WCF proxy uses the event asynchronous pattern which involves wiring up an event completion handler and then calling a method to kick off the operation. The operations are all asynchronous and the final result is reported back on a worker thread through the event handler. This is an older technique which was created prior to the Task Parallel Framework and async / await keywords in C# 5. However, it turns out we can easily turn these methods into Task-based approaches to make them easier to consume in our mobile app. As an example, let's do this for the Login.

string authorizationKey;
...
public Task<bool> Login()
{
    var tcs = new TaskCompletionSource<bool>();

    var client = GetClient();

    client.LoginCompleted += (sender, e) => {
        if (e.Cancelled) {
            tcs.SetCanceled();
        } else if (e.Error != null) {
            tcs.SetException(e.Error);
        } else {
            authorizationKey = e.Result;
            tcs.SetResult(!string.IsNullOrEmpty(authorizationKey));
        }
    };

    client.LoginAsync();

    return tcs.Task;
}

The key to this approach is the TaskCompletionSource class. You don't use this class very often, but it's quite handy in this specific case where we are converting an older asynchronous style into the more modern Task-based approach.

First, we create a TaskCompletionSource<T> where T is the expected result from the asynchronous operation. Then we call our GetClient method to get the configured BooksClient object. We want to use the LoginAsync method, so first we wire up to the LoginCompleted event and add a handler to process the result. Then we call the LoginAsync method to kick off the operation. Finally, we return the Task property of the TaskCompletionSource.

In the completion event handler, we examine the passed event arguments to determine if the method was canceled, failed or was successful and trigger the appropriate flags on the TaskCompletionSource. If it was successful, we store off the authorization key and set the result of the completion source to a true/false value.

The client usage for this method is to use async / await just as they would on any Task based operation, the TaskCompletionSource provides the task mechanics without a real thread, instead relying on the WCF proxy thread to do the work. All of our methods will be implemented using this exact same pattern.

Add the authentication token to the HTTP header

The next step is to create a shared method which will add the authorization key we just obtained to the header collection of the WCF proxy when we call any of the other methods. We'll do this by calling a method before all our other calls to add the header information.

  1. Add a new private method to the class named AddHeader. It should return a Task so we can retrieve any errors from the asynchronous Login call.
  2. If the authorization field is empty or blank, then call the Login method you just created to get the authentication token. Use async / await to do this without blocking the UI thread.
  3. Next, we need to take the string and add it as header to any outgoing request. In WCF, this is done by modifying the OperationContext which is a static class that is valid while you are processing a method call. We'll just assume one is present in this method and then make sure that all our actual client methods create one before calling this. Use the following code, you will need to add the System.ServiceModel.Channels namespace (just use ALT+ENTER on the unknown classes in red to get the IDE to add the namespace for you).
private async Task AddHeader()
{
    if (string.IsNullOrEmpty(authorizationKey)) {
        await Login();
    }

    // Add a SOAP Header to an outgoing request
    MessageHeader header = MessageHeader.CreateHeader(
        "Authorization", "", authorizationKey);
    OperationContext.Current.OutgoingMessageHeaders.Add(header);

    // Add the HTTP header version
    HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
    httpRequestProperty.Headers["Authorization"] = authorizationKey;
    OperationContext.Current
        .OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
}

This code adds the header into two areas. The first block adds the header into the SOAP headers - this is in the XML envelope we discussed in the lecture and this is often where any out-of-band data would be placed. The second block adds it to the HTTP headers collection which is where our server expects it to be. We've included the first block just so you can see how to affect the SOAP headers, but it's technically not necessary for our service here. (Although it won't hurt to include it either - the service just won't look at it).

Get the list of books

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, locate the method named GetAll which returns a Task<IEnumerable<Book>> and delete the current implementation.
  2. Create a TaskCompletionSource<IEnumerable<Book>> to hold the result.
  3. Use your GetClient method to retrieve a BooksClient to work with.
  4. Use GetBooksAsync method on the proxy and the GetBooksCompleted event to process the return result.
  5. The code structure should look almost identical to the Login method - returning the Task of the TaskCompletionSource. Remember to mark the method as async.
  6. Once you've got that in place, we need to add our header information and create an OperationContext to work with (remember we said we'd need that). We do this by creating a OperationContextScope for the BooksClient, and this must be disposed when we are finished with it. Since this is a bit confusing, here's the code you will need to add - fill in the TODO below:
public async Task<IEnumerable<Book>> GetAll()
{
    var tcs = new TaskCompletionSource<IEnumerable<Book>>();
    var client = GetClient();

    using (var context = new OperationContextScope(client.InnerChannel)) {
        await AddHeader();

        // TODO: Your code to call the service goes here - 
        // use the client.GetBooksAsync method and GetBooksCompleted event.
        // structure it like you did Login.

        return await tcs.Task;
    }
}
  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 GetBooksAsync call to retrieve the books.

Show Code

The remainder of the methods follow the same style as what you have done already and are mostly copy/paste from the existing GetAll method you just created. Feel free to open the completed solution to walk through the code, or you can continue adding support for Add, Update and Delete if you have time remaining in the exercise.

Add a new book

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

  1. Open the BookManager code. 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 properties with the passed data:
    • Set the ISBN property to an empty string.
    • Make sure to create a new string[] 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 CreateBookAsync method and the CreateBookCompleted event to create the new book.
  5. Follow the structure of the GetAll method - make sure to call AddHeader and to wrap the call in an OperationContextScope block to pass the headers along to the service. In this case, your TaskCompletionSource should return a book. Remember to mark the method as async.
  6. Build and run the application to try the logic - just press the Add button (looks like a [+] in the toolbar). Debug through the logic if it's not working properly. When you press the Add Book button, it should add the book and return you back to the main screen.

Show Code

Update an existing book

We are now able to display and add new books, let's 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 UpdateBookAsync method to send a book to service. Use the UpdateBookCompleted event to catch the completion.
  3. 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.
  4. However, there isn't a non-generic version of TaskCompletionSource, so have it return a string which is the response message from the service (e.Result.Message). Since Task<TResult> derives from Task, it is acceptable to return this and the client is unaware there is a bit of data tagged with it. Note: we could just as easily use a boolean value.

Show Code

Delete a book

The final operation we have is Delete. The code should be structured just like the update case.

  1. Open the BookManager class and find the method named Delete which takes a string and returns a Task.
  2. In the method body, get a new client and use the DeleteAsync method and the DeleteBookCompleted event handler. The response returns a status message just like the update did.
  3. Add a call to the new delete method into the OnDeleteBook method in MainPage.xaml.cs. There's a comment marker where the code should go.
  4. Finally, make sure to remove the book from the books collection when the web call is complete to update the UI.
  5. 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).

Show Code

Summary

Congratulations! You have successfully utilized a SOAP service within your Xamarin application to work with a set of books.

Go Back