Downloading data using a Portable Class Library for Windows Phone and Windows 8

Downloading data using a Portable Class Library for Windows Phone and Windows 8

We’re going to take a bit of a side trip before we return to the data binding series. In this post we’re going to look at how to download data from a remote service. We’ll be using a json feed but this post should give you enough background to be able to download content from any service. There are two aspects to this post that I want to highlight:

1) We’ll be writing all the downloading and deserialization logic in a portable class library, which means it can be used across any number of target platforms.

2) We’ll be using the async-await pattern, which will greatly improve the readability of our code. I’ll illustrate the old way of downloading data for a point of reference only. I’d highly recommend getting on the asynchronous bandwagon “why await when you can async!”

We’ll be accessing a sample OData feed which is available via odata.org (http://services.odata.org/V3/OData/OData.svc/Products), which lists a number of products. You can either use the OData API Explorer (http://services.odata.org/ODataAPIExplorer/ODataAPIExplorer.html) or other tools such as Fiddler which will allow you to send http requests. The following image illustrates the formatted json response from a call to the Products url.

image

Since we want to run this across both Windows Phone and Windows I’ve got a solution with both a Windows Phone and a Windows 8 application. To this solution we’ll add a new Portable Class Library.

image 

When prompted I tweak the target frameworks – I’m not particularly interested in being limited by previous frameworks so I uncheck support for Silverlight and for Windows Phone 7.x.

image

 

Now to write some code – we’ll start with the old school way of downloading data. The following method creates a WebRequest. In order to get json data back from the OData site we need to specify the application/json Accept header. Calling BeginGetResponse invokes the GET request to retrieve data. The argument to this method is an anonymous method which is invoked when the request returns. After decoding the response, the callback passed into the DownloadJsonTheOldWay method is invoked.

public static void DownloadJsonTheOldWay<TData>
        (string url,Action<TData> callback)
{
        var req = WebRequest.CreateHttp(url);
        req.Accept = "application/json";
        req.BeginGetResponse(cb =>
        {
            var cbreq = cb.AsyncState as WebRequest;
            var resp = cbreq.EndGetResponse(cb);
            var serializer = new DataContractJsonSerializer(typeof(TData));
            using (var strm = resp.GetResponseStream())
            {
                var result = (TData)serializer.ReadObject(strm);
                callback(result);
            }
                    
        }, req);
}

 

Invoking this from our applications looks like:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    Helper.DownloadJsonTheOldWay<ProductData>
       ("http://services.odata.org/V3/OData/OData.svc/Products",DataFound);
}
 
private void DataFound(ProductData data)
{
    Debug.WriteLine(data!=null);
}

This is less than ideal as it means our application code is jumping around – easy to see in this isolated example but once our application grows in complexity it’ll be much harder to follow the flow of execution. Let’s try to improve this so that our code doesn’t jump around so much:

public static Task<TData> DownloadJsonTheOldWay<TData>(string url)
{
        var req = WebRequest.CreateHttp(url);
        req.Accept = "application/json";
        TData result = default(TData);
        var waiter = new ManualResetEvent(false);
        req.BeginGetResponse(cb =>
        {
            var cbreq = cb.AsyncState as WebRequest;
            var resp = cbreq.EndGetResponse(cb);
            var serializer = new DataContractJsonSerializer(typeof(TData));
            using (var strm = resp.GetResponseStream())
            {
                result = (TData)serializer.ReadObject(strm);
            }
            waiter.Set();
        }, req);
        waiter.WaitOne();
        return result;
}

 

In this code we’re using a ManualResetEvent to block the method until the request returns. Unfortunately if this method is invoked from the UI thread it will block that thread until the request returns – this is fine for Windows 8 but Windows Phone uses the UI thread as part of invoking web requests. The net effect is that this code will block indefinitely on Windows Phone.

Right, so we need to find a way to prevent the UI thread from being blocked. This can be done by invoking the previous attempt on a different thread. In fact we can leverage the async-await pattern:

public async static Task<TData> DownloadJsonTheOldWay<TData>(string url)
{
    return await Task.Run(() =>
        {
            var req = WebRequest.CreateHttp(url);
            req.Accept = "application/json";
            TData result = default(TData);
            var waiter = new ManualResetEvent(false);
            req.BeginGetResponse(cb =>
                {
                    var cbreq = cb.AsyncState as WebRequest;
                    var resp = cbreq.EndGetResponse(cb);
                    var serializer = new DataContractJsonSerializer(typeof(TData));
                    using (var strm = resp.GetResponseStream())
                    {
                        result = (TData)serializer.ReadObject(strm);
                    }
                    waiter.Set();
                }, req);
            waiter.WaitOne();
            return result;
        });
}

Now, the code in our application looks much cleaner:

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
 
        var data = await Helper.DownloadJsonTheOldWay<ProductData>
                        ("http://services.odata.org/V3/OData/OData.svc/Products");
        Debug.WriteLine(data != null);
}

 

In fact, the pattern of BeginXXX, EndXXX is quite common amongst traditional .NET apis. So much so that as part of the asynchronous programming library there is a Factory method which can be used to simplify this code somewhat:

public async static Task<TData> DownloadJson<TData>(string url)
{
    var req = WebRequest.CreateHttp(url);
    req.Accept = "application/json";
    var resp = await Task.Factory.FromAsync<WebResponse>
                    (req.BeginGetResponse, req.EndGetResponse, null);
    var serializer = new DataContractJsonSerializer(typeof(TData));
    using (var strm = resp.GetResponseStream())
    {
        var data = (TData)serializer.ReadObject(strm);
        return data;
    }
}

 

The Factory.FromAsync brings together the BeginGetResponse and EndGetResponse into a single line to return the response of the request. This works well on Windows 8. However, on Windows Phone an exception is thrown as read stream buffering needs to be enabled. This is done by adding “req.AllowReadStreamBuffering = true;” before invoking the request. Unfortunately whilst adding this means the code works for Windows Phone, it then breaks for Windows 8.

The solution, and the last piece to our code, is some conditional logic to determine if the code is being invoked on Windows RT (ie Windows 8) or not. If it is, then read stream buffering is not enabled.

private static bool IsWinRT { get; set; }
static Helper ()
{
    var wrt = Type.GetType("Windows.ApplicationModel.DesignMode, Windows, ContentType=WindowsRuntime");
    if (wrt != null)
    {
        IsWinRT = true;
    }
 
}
public async static Task<TData> DownloadJson<TData>(string url)
{
    var req = WebRequest.CreateHttp(url);
    if (!IsWinRT)
    {
        req.AllowReadStreamBuffering = true;
    }
    req.Accept = "application/json";
    var resp = await Task.Factory.FromAsync<WebResponse>
              (req.BeginGetResponse, req.EndGetResponse, null);
    var serializer = new DataContractJsonSerializer(typeof(TData));
    using (var strm = resp.GetResponseStream())
    {
        var data = (TData)serializer.ReadObject(strm);
        return data;
    }
}

We can invoke this method from either Windows Phone or Windows 8, giving us a single line way to download and decode json from any remote uri.

image

Leave a Reply

Your email address will not be published. Required fields are marked *