WinRT vs. Silverlight - Part 7 - Making WebRequests

See intro blogpost here.

In Silverlight and WPF you are probably used to using WebClient to perform downloads. This is a simple class for doing download/upload string or retrieving a response stream from the server. However, in Windows Runtime this client doesn’t exists. Instead we have new a simple “HttpClient” to do much of what WebClient can. However it works very differently by using the new Task framework, and the more you dive into porting code to WinRT, you will see this a lot, and it will make your life porting code cumbersome. On the upside, the Tasks framework is awesome and it’ll often simplify you code a great deal! I talked a bit about this in my previous post, so please read that first, before you continue here, since this will be building on top of that.

Let’s say we have a method in WPF and Silverlight that uses WebClient to downloada and create an image. Since this is working asynchronously, we would have to either use event handlers or action delegates to return the result and/or the error. The method could look something like this:

public void GetContent(Uri uri, Action<string> complete, Action<Exception> error)
{
    WebClient client = new WebClient();
    client.DownloadStringCompleted += (sender, e) =>
    {
        if (e.Error != null)
            error(e.Error);
        else {
            complete(e.Result);
        }
    };
    client.DownloadStringAsync(uri);
}

 

Here’s how a method like that could look in WinRT:

public async Task<string> GetContentI(Uri uri)
{
   System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
   var result = await client.GetAsync(uri);
   return result.Content.ReadAsString();
}

Simple right? Well it' gets even simpler when we have to start consuming that method.

From SL and WPF you would consume the method something like this:

GetContent(uri, (text) => {
          TextBlock tb = new TextBlock() { Source = text };
          LayoutRoot.Children.Add(tb);
      },
      (error) => { /*TODO: handle error*/ }
);

And the same using the Task framework:

try {
   TextBlock tb = new TextBlock() { Source = await GetContent(uri) };
   LayoutRoot.Children.Add(tb);
catch { /*TODO: handle error */ }

You tell me what’s more elegant and readable? Smile

My point here is that Tasks are awesome, so rather than trying to reuse your existing code in WinRT, consider rewriting your existing code to use Tasks and it will work much smoother.

There’s lets create a method for downloading a string over the network that works the same way across the board. (I’ll assume you are using the Async Task CTP for Silverlight or WPF for this).

public async Task<string> GetContent(Uri uri)
{
#if NETFX_CORE
    System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
    var reqResult = await client.GetAsync(uri);
    return reqResult.Content.ReadAsString();
#else
    WebClient client = new WebClient();
    TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
    client.DownloadStringCompleted += (sender, e) =>
    {
        if (e.Error != null)
            tcs.TrySetException(e.Error);
        else
            tcs.TrySetResult(e.Result);
    };
    client.DownloadStringAsync(uri);
    return await tcs.Task;
#endif
}

Note you could also use the new .NET method “WebClient.DownloadStringTaskAsync” which would simplify the above quite a lot. I used the event based approach to demonstrate how you would handle the cases where you don’t already have a task implementation available.

Comments (4) -

  • Thanks for the WRT posts.  They are helpful.

    We developed some REST endpoints in the form of a both a proxy web site as well
    as a Server Object Extension.  That coarse grained functionality is then consumed
    by business objects which execute in Silverlight.  Today, those business objects
    are compiled into an assembly for Silverlight was well as an assembly for the full
    .NET.  Essentially, we are following the same design pattern that you did.

    What we want to do is to resuse those same business objects in WinRT as well.  If we
    can do that then we have the ability to produce some great Metro products as soon
    as Windows 8 is released which is pretty cool.

    That gets complex because it looks like we need SL 5 and VS 2011 (.NET 4.5) and
    that all of this must run on the older OS's such as XP.  That means that ESRI must
    support all of this as well as Microsoft.

    I really like how all the new functionaly makes writing async code very clean.
  • Richard: WinRT apps will not run on WinXP, Vista or 7, so no need to support a WinRT API on those platforms.
    Also you do not need Silverlight 5 for Win8. This is not Silverlight (but if you need Silverlight s well, earlier versions will run just as fine on Win8).
    As always with the latest Microsoft SDKs, the latest Visual Studio is needed.
  • Thanks for the response Morten.  I understand what you said.

    The thing that I want to do is to come up with single set of source code for business objects with an API which is consistent across Silverlight, WinRT, and .NET (non-WinRT).
  • I know that this isn't exactly the right place, but there doesn't seem to be a better one.  You seem to be quite knowledgable, though, so perhaps you could move it to the right place and/or post an entry relating to this issue.

    Google tells me that I'm not the only one facing this problem: we are building a WPF application using the Esri WPF API, but in order to consume REST services we have to go through a proxy.  That would be fine, except that the proxy requires authentication.  All of the WebClient libraries support the Proxy object, which in turn supports a Credentials object.  The Esri API, though, only offers me a "ProxyURL" string, and a separate Credentials object, which doesn't appear to do anything for the proxy; I wouldn't expect it to.  That object seems to be for providing creds for the REST endpoint.

    "ProxyURL" is obviously for supporting a "proxy page", which is not applicable for our situation - that's not what we're doing.  We'd just like to consume simple, insecure maps and services like ArcGIS Online.

    One workaround that we've found is to build - in the application itself - our own tiny webserver/proxy that uses WebClient to pass http requests through to the company proxy.  That seems very kludgy, though. How can we get through the proxy without writing our own WebClient-based proxy into our application?

Pingbacks and trackbacks (3)+

Add comment