views:

139

answers:

3

Hi all,

I have been experimenting with WP7 apps today and have hit a bit of a wall. I like to have seperation between the UI and the main app code but Ive hit a wall.

I have succesfully implemented a webclient request and gotten a result, but because the call is async I dont know how to pass this backup to the UI level. I cannot seem to hack in a wait for response to complete or anything. I must be doing something wrong.

(this is the xbox360Voice library that I have for download on my website: http://www.jamesstuddart.co.uk/Projects/ASP.Net/Xbox_Feeds/ which I am porting to WP7 as a test)

here is the backend code snippet:

    internal const string BaseUrlFormat = "http://www.360voice.com/api/gamertag-profile.asp?tag={0}";
    internal static string ResponseXml { get; set; }
    internal static WebClient Client = new WebClient();

    public static XboxGamer? GetGamer(string gamerTag)
    {
        var url = string.Format(BaseUrlFormat, gamerTag);

        var response = GetResponse(url, null, null);

        return SerializeResponse(response);
    }

    internal static XboxGamer? SerializeResponse(string response)
    {
        if (string.IsNullOrEmpty(response))
        {
            return null;
        }

        var tempGamer = new XboxGamer();
        var gamer = (XboxGamer)SerializationMethods.Deserialize(tempGamer, response);

        return gamer;
    }

    internal static string GetResponse(string url, string userName, string password)
    {


            if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
            {
                Client.Credentials = new NetworkCredential(userName, password);
            }

            try
            {
                Client.DownloadStringCompleted += ClientDownloadStringCompleted;
                Client.DownloadStringAsync(new Uri(url));

                return ResponseXml;
            }
            catch (Exception ex)
            {
                return null;
            }
        }



    internal static void ClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            ResponseXml = e.Result;
        }
    }

and this is the front end code:

public void GetGamerDetails()
{
    var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
    var xboxGamer = xboxManager.GetGamer();

    if (xboxGamer.HasValue)
    {
        var profile = xboxGamer.Value.Profile[0];
        imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
        txtUserName.Text = profile.GamerTag;
        txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
        txtZone.Text = profile.PlayerZone;
    }
    else
    {
        txtUserName.Text = "Failed to load data";
    }
}

Now I understand I need to place something in ClientDownloadStringCompleted but I am unsure what :(

Any help would be great,

Thanks,

A: 

You generally have 2 options. Either you expose your backend code as an async API as well, or you need to wait for the call to complete in GetResponse.

Doing it the async way would mean starting the process one place, then return, and have the UI update when data is available. This is generally the preferred way, since calling a blocking method on the UI thread will make your app seem unresponsive as long as the method is running.

driis
Do you have any examples of this, as I understand 'how I could do it' but I dont know how to implement it. I havent done any SL before either so Im on a steep learning curve.
JamesStuddart
A: 

I think the "Silverlight Way" would be to use databinding. Your XboxGamer object should implement the INotifyPropertyChanged interface. When you call GetGamer() it returns immediately with an "empty" XboxGamer object (maybe with GamerTag=="Loading..." or something). In your ClientDownloadStringCompleted handler you should deserialize the returned XML and then fire the INotifyPropertyChanged.PropertyChanged event.

If you look at the "Windows Phone Databound Application" project template in the SDK, the ItemViewModel class is implemented this way.

John Vert
Thanks, this looks like what I might be looking for. I did look at the template (as in looked at it in the 'new project' window, but didnt create one) Ill give it a go.
JamesStuddart
+1  A: 

The problem you have is that as soon as an asynchronous operation is introduced in to the code path the entire code path needs to become asynchronous.

  • Because GetResponse calls DownloadStringAsync it must become asynchronous, it can't return a string, it can only do that on a callback
  • Because GetGamer calls GetResponse which is now asynchronous it can't return a XboxGamer, it can only do that on a callback
  • Because GetGamerDetails calls GetGamer which is now asynchronous it can't continue with its code following the call, it can only do that after it has received a call back from GetGamer.
  • Because GetGamerDetails is now asynchronous anything call it must also acknowledge this behaviour.
  • .... this continues all the way up to the top of the chain where a user event will have occured.

Here is some air code that knocks some asynchronicity in to the code.

public static void GetGamer(string gamerTag, Action<XboxGamer?> completed) 
{ 
    var url = string.Format(BaseUrlFormat, gamerTag); 

    var response = GetResponse(url, null, null, (response) =>
    {
        completed(SerializeResponse(response));
    }); 
} 


internal static string GetResponse(string url, string userName, string password, Action<string> completed)      
{      

   WebClient client = new WebClient();
   if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))      
   {      
       client.Credentials = new NetworkCredential(userName, password);      
   }      

   try      
   {      
        client.DownloadStringCompleted += (s, args) =>
        {
           // Messy error handling needed here, out of scope
           completed(args.Result);
        };
        client.DownloadStringAsync(new Uri(url));        
   }      
   catch     
   {      
      completed(null);      
   }      
}      


public void GetGamerDetails()              
{              
    var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");              
    xboxManager.GetGamer( (xboxGamer) =>              
    {
         // Need to move to the main UI thread.
         Dispatcher.BeginInvoke(new Action<XboxGamer?>(DisplayGamerDetails), xboxGamer);
    });

} 

void DisplayGamerDetails(XboxGamer? xboxGamer)
{
    if (xboxGamer.HasValue)              
    {              
        var profile = xboxGamer.Value.Profile[0];              
        imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));              
        txtUserName.Text = profile.GamerTag;              
        txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");              
        txtZone.Text = profile.PlayerZone;              
    }              
    else              
    {              
        txtUserName.Text = "Failed to load data";              
    }         
}

As you can see async programming can get realy messy.

AnthonyWJones
"As you can see async programming can get realy messy." ... which is one reason to use F#. With F# you can refactor the code to async without having to do all the callback-inversion-of-control somersaults.
Brian
@Brian: I completely agree. I just wish functional programming could be made more accessible, it probably is to younger minds but no matter how hard I try I can't get the concepts to stick in mine.
AnthonyWJones
ill give this a go tomorrow thanks
JamesStuddart