Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good Async pattern for sequential WebClient requests

Most of the code I've written in .NET to make REST calls have been synchronous. Since Silverlight on Windows Phone only supports Async WebClient and HttpWebRequest calls, I was wondering what a good async pattern is for a Class that exposes methods that make REST calls.

For example, I have an app that needs to do the following.

  1. Login and get token
  2. Using token from #1, get a list of albums
  3. Using token from #1 get a list of categories
  4. etc

my class exposes a few methods:

  1. Login()
  2. GetAlbums()
  3. GetCategories()

since each method needs to call WebClient using Async calls what I need to do is essentially block calling Login till it returns so that I can call GetAlbums().

What is a good way to go about this in my class that exposes those methods?

like image 215
Omar Shahine Avatar asked Mar 19 '10 16:03

Omar Shahine


4 Answers

You might take a look at the Reactive (Rx) framework extensions:

http://www.leading-edge-dev.de/?p=501

http://themechanicalbride.blogspot.com/2009/07/introducing-rx-linq-to-events.html

[edit: ooh - found a good link:] http://rxwiki.wikidot.com/101samples

They provide a way to "sequence" events, acting only upon certain conditions met - for example, say you had a method "AuthenticationResult Authenticate(string user, string pass)"

You could do something like:

var foo = Observable.FromAsyncPattern<string, string, AuthenticationResult>
    (client.BeginAuthenticate, client.EndAuthenticate);
var bar = foo("username","password");
var result = bar.First();

Effectively turning an asynchronous method to a synchronous one. You can extend this to include "chaining":

var bar = foo("username", "password")
    .Then(authresult => DoSomethingWithResult(authresult));

Neat stuff. :)

like image 136
JerKimball Avatar answered Oct 11 '22 21:10

JerKimball


It really depends on what you want to do with this information. If for instance you are attempting to display the list of albums/categories etc, one way to model this would be to

  1. Have one or more classes which implements the INotifyPropertyChanged interface, and are used as data sources for your views (look at the files under the Models folder in a new PhoneListApplication for an example)
  2. Start an async operation to login and get the token, have the async method's callback store the token for you and call a function which will start an async operation to get list of albums and categories.
  3. The callback for the async operation to get a list of albums/categories can update an ObservableList (by adding items to it). I'd imaging you have one class each for albums and categories, each with an observable list. Anyways, once you are done adding, just call NotifyPropertyChanged with the name of the property you changed, and your data should show up.

There is an obvious problem with cases where you want to wait and not proceed until you receive something back over the network (for instance if you want to keep the login page around until you know that you have successfully authenticated). In this case you could just change the page in the async callback.

You could obviously also do something fancier and have a thread wait for an event set by the async callback. I recommend not have the UI thread do this, since it limits your ability to have things like timeouts, and is generally very messy.

like image 28
Aurojit Panda Avatar answered Oct 11 '22 22:10

Aurojit Panda


We wrote our client-side service layer with all async function signatures that look like this:

public void MyFunction(
  ArtType arg, 
  Action<ReturnType> success, 
  Action<FailureType> failure);

The service code does an async call to the web service, and when that returns it calls the success callback if the call was successful, and the failure callback if there was a fault/exception. Then the calling code kinda looks like this:

MyServiceInstance.MyFunction(
  blahVar,
  returnVal => UIInvoker.Invoke(() => 
    {
      //some success code here
    }),
  fault => UIInvoker.Invoke(() => 
    {
      //some fault handling code here
    }));

(UIInvoker is just a utility that dispatches back to the UI from a background thread.)

like image 45
Jason Jackson Avatar answered Oct 11 '22 23:10

Jason Jackson


I put something together that is a bit more fluent.

Restful-Silverlight is a library I've created to help with both Silverlight and WP7.

I have included code below to show how you can use the library to retrieve tweets from Twitter.

Sample Usage of Restful-Silverlight retrieving tweets from Twitter:


//silverlight 4 usage
List<string> tweets = new List<string>();
var baseUri = "http://search.twitter.com/";

//new up asyncdelegation
var restFacilitator = new RestFacilitator();
var restService = new RestService(restFacilitator, baseUri);
var asyncDelegation = new AsyncDelegation(restFacilitator, restService, baseUri);

//tell async delegation to perform an HTTP/GET against a URI and return a dynamic type
asyncDelegation.Get<dynamic>(new { url = "search.json", q = "#haiku" })
    //when the HTTP/GET is performed, execute the following lambda against the result set.
    .WhenFinished(
    result => 
    {
        textBlockTweets.Text = "";
        //the json object returned by twitter contains a enumerable collection called results
        tweets = (result.results as IEnumerable).Select(s => s.text as string).ToList();
        foreach (string tweet in tweets)
        {
             textBlockTweets.Text += 
             HttpUtility.HtmlDecode(tweet) + 
             Environment.NewLine + 
             Environment.NewLine;
        }
    });

asyncDelegation.Go();

//wp7 usage
var baseUri = "http://search.twitter.com/";
var restFacilitator = new RestFacilitator();
var restService = new RestService(restFacilitator, baseUri);
var asyncDelegation = new AsyncDelegation(restFacilitator, restService, baseUri);

asyncDelegation.Get<Dictionary<string, object>>(new { url = "search.json", q = "#haiku" })
               .WhenFinished(
               result =>
               {
                   List<string> tweets = new List();
                   textBlockTweets.Text = "";
                   foreach (var tweetObject in result["results"].ToDictionaryArray())
                   {
                       textBlockTweets.Text +=
                           HttpUtility.HtmlDecode(tweetObject["text"].ToString()) + 
                           Environment.NewLine + 
                           Environment.NewLine;
                   }
               });

asyncDelegation.Go();

like image 36
Amir Avatar answered Oct 11 '22 22:10

Amir