(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Windows Phone 7 Data Access Strategies: WebClient

(7 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   72
Comments:   9
More Articles
8 comments   /   posted on Aug 17, 2011
Categories:   Windows Phone

Download sample code


When you write an application, no matter the platform you are running on, most of the times you need to access a data source that is often located remotely. This is mostly true when your application will run on a mobile device that is connected to the Internet because you usually have to call an application server that is providing some kind of data to display on the device screen or you have to send to it information you acquired from the user.

In this article I will cover the networking tools that Silverlight for Windows Phone provides, to access remote resources. No matter the resource you have to access, it implies you have to send or receive something in a serialized form that both the client and the server understand using a serialization/deserialization process. Also if we will have to deal with serialization this argument is not covered here but we will simply touch it for the purpose of demonstrating the networking tools.

Starting from the simplest

There is a wide set of tools, each one with its own peculiarity that makes it specific to some kind of scenario and gives you more or less control on what it is happening under the hood when you place a call to the network. In this article we will not start from the lower one, but from the simplest one, the WebClient.

The WebClient is a widely adopted tool that merges simplicity and flexibility and it is usually the best choice for mostly of the networking needs. It is built over the HttpWebRequest that is probably the most low level tool - I will cover it in the next number - but its purpose is making networking simple, removing the need of dealing with the low level Request and Response. Due to this, the WebClient cannot be used when you need to access the basic HTTP protocol features but most of the times it is more than enough.

Once you created an instance of the WebClient class you have to be aware that it can be used and reused many times to place multiple network calls but it does not support concurrent calls, so you have to manage to wait for the completion of the previous call before starting another one. If you need to place two multiple concurrent calls you have to create a different instance, one for each one.

The WebClient class provides a number of ways to call an http resource. If you scan the methods you will find the following:

  • DownloadStringAsync: Provides the simplest way of downloading a string resource from the network, like an HTML page, a RSS or ATOM feed, a plain XML file and so on.
  • UploadStringAsync: It can be used to send strings to a server and it can also specify the method to be used to make the call. Probably it is the simplest way of making calls to a REST resource
  • OpenWriteAsync & OpenReadAsync : These two method allow to send and receive binary resources to/from the server. The use is slightly more complex that the other methods but once the call completes you can access a binary stream you can read or write directly.

Side by side with each method there are some objects that reveal a common pattern you have to use when you call the network using WebClient. Using the DownloadStringAsync as an example you can discover an event DownloadStringCompleted, a DownloadStringCompletedEventHandler delegate and the relative EventArgs class DownloadStringCompletedEventArgs. This is because the WebClient exposes an asynchronous model that is event-driven instead of callback-driven. From a point of view this makes the class simpler but from another point of view this complicates the use of the class because the event model make difficult to handle the call in a single point: Here is the common example to make things clear:

public void GetFeed()
{
    WebClient client = new WebClient();
    client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
    client.DownloadStringAsync(new Uri("http://www.slpg.org/syndication.axd"));
}
 
void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    string data = e.Result;
    // do something with the feed here
}

This pattern is for sure simple because it requires a few lines of code but also it opens a common problem. You have to place the calls directly in the page, merging the data access layer into the UI, because of the event driven model. With a bit more work you can create a data access layer that is also event-driven but it easily becomes full of methods, events and delegates. To solve this problem I usually use the following pattern that takes advantage of lambda expressions:

public void GetFeed(Action<string> result)
{
    DownloadStringCompletedEventHandler handler = null;
 
    handler = (s, e) =>
    {
        this.Client.DownloadStringCompleted -= handler;
        result(e.Result);
    };
 
    this.Client.DownloadStringCompleted += handler;
    this.Client.DownloadStringAsync(new Uri("http://www.slpg.org/syndication.axd"));
}

In the example I create an instance of the DownloadStringCompletedEventHandler then I use it to attach the relative event of the WebClient instance that is common to all the methods. In the handler, I take also care of detaching the event when the call is completed. This is to avoid memory leaks caused by multiple event-handlers attached to the class. Finally I place the call and into the event handler I call an Action<string> instance that is used to return the result asynchronously to the caller. This makes the event-driven model again callback-driven and makes easy to place the calls and abstract the application levels:

this.GetFeed(s => this.HandleReceivedFeed(s));

Handling Errors and Cancellation

Calling the network to access a resource poses the problem of handling errors. The errors you can get by the WebClient are caused by both network issues or by applicative conditions. Since the first kind of errors - timeouts, protocol violations, communication issues - are represented by an Exception object, the applicative part of the problem must be handled by your code because the WebClient is only able to upload or download data from the network but is not aware of the application exceptions and it not automatically serializes to transmit them along the network.

Also when you place the call you can figure out the user requests to cancel the operation - as an example because it is taking longer than expected - and the WebClient supports cancellation using the CancelAsync method. It tries to cancel the current call in an non-blocking way.

When you get the answer from the event handler you have the opportunity to handle both the network exceptions and the cancellation occurred before the completion. The EventArgs contains an Error property that exposes the exception you got by the call - null if the call completes without errors - and a Cancelled property that is a boolean that indicates that a cancellation is occurred. Given these two new properties here is how you can handle them using my pattern:

public void GetFeed(Action<string> success, Action<Exception> fail)
{
    DownloadStringCompletedEventHandler handler = null;
 
    handler = (s, e) =>
    {
        this.Client.DownloadStringCompleted -= handler;
        
        if (!e.Cancelled)
        {
            if (e.Error == null)
                success(e.Result);
            else
                fail(e.Error);
        }
    };
 
    this.Client.DownloadStringCompleted += handler;
    this.Client.DownloadStringAsync(new Uri(http://www.slpg.org/syndication.axd));
}
In my pattern I'm used to forward the error condition to the caller so it can decide whether to display the error or swallow the exception. In the case Cancelled is true, I simply omit to call anything and the method will gracefully mask the cancellation condition.

Handling download progress

Lot of times, when you download an huge file like a video, a set of images or also some initialization data, it is important to give a feedback to the user that is waiting about the current position of the stream. The WebClient class easily solves this scenario providing some events that let you know about the download progress. Both DownloadStringAsync and UploadStringAsync have respectively the DownloadProgressChanged and UploadProgressChanged that are able to notify the download status in terms of BytesReceived and TotalBytesToReceive or simply ProgressPercentage.

public DataProviderWebClient()
{
    this.Client = new WebClient();
 
    this.Client.DownloadProgressChanged += 
        new DownloadProgressChangedEventHandler(Client_DownloadProgressChanged);
}
 
public void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    DownloadProgressChangedEventHandler handler = this.ProgressChanged;
 
    if (handler != null)
        handler(this, e);
}

In this example I handle the progress events at the DataProvider level and I forward the incoming notification as events to the instance owner. To me it is not useful to add another lambda expression to the methods to expose the download progress but, knowing that the download notification in the user interface will be unique, I've simply exposed the event and I globally handle it on the DataProvider switching a progress bar on or off according with the notified percentage. If your provider uses both UploadString and DownloadString you will have to create your own ProgressEventArgs and map the resulting events from the methods to your class.

Advanced topics: Headers, Verbs, Credentials and Cookies

There are a set of advanced features you may have the need of handle in the HTTP protocol. As already said WebClient does not provides a low level access to the protocol but what it exposes is enough to easily handle most of the needs. First of all, the class exposes a set of Headers, both for request and response. Using these collections you can read headers coming from the server or add your own. For security purpose not all the headers are available; as an example the Referer header could not be set. For a complete list of restricted headers follow this link: http://msdn.microsoft.com/en-us/library/system.net.webheadercollection(v=VS.95).aspx

Obviously you can add your own custom HTTP headers for the purpose of your application protocol:

this.Client.Headers["x-source-uri"] = http://www.silverlightplayground.org;

Working with RESTful resources imply you have to use http verbs to indicate the action you have to perform. In the example you can download at the beginning of the article I used a REST paradigm to get a list of names and to add and remove names. The protocol is made of an xml file containing a set of names. When I call the ashx handler with the GET verb I retrieve the list of names. If I upload the same xml file with a list of names using the POST verb the names are added to the list and with the DELETE verb they are removed. To use verbs (the so called HTTP methods) you have to use the UploadStringAsync method:

public void AddNames(IEnumerable<string> names, Action success, Action<Exception> fail)
{
    this.UploadNames(names, "POST", success, fail);
}
 
public void DeleteNames(IEnumerable<string> names, Action success, Action<Exception> fail)
{
    this.UploadNames(names, "DELETE", success, fail);        
}
 
private void UploadNames(IEnumerable<string> names, string method, Action success, Action<Exception> fail)
{
    UploadStringCompletedEventHandler handler = null;
 
    handler = (s, e) =>
    {
        this.Client.UploadStringCompleted -= handler;
 
        if (!e.Cancelled)
        {
            if (e.Error == null)
                success();
            else
                fail(e.Error);
        }
    };
 
    this.Client.UploadStringCompleted += handler;
    this.Client.UploadStringAsync(new Uri("http://localhost:8000/HttpHandler.ashx"), method, this.Serialize(names));
}

Finally, lot of times, you have to authenticate to a resource and this require also support for the cookies to be authenticated across different calls. The WebClient class supports the Credentials property you can use to specify username and password to authenticate to the server.

this.Client.Credentials = new NetworkCredential("username", "p@ssw0rd");

Unfortunately WebClient does not natively support the cookies but with a simple workaround you can extend this class to add a CookieContainer that enables the cookies for all the calls. To enable this scenario you have to extend the WebClient class to create an ExtendedWebClient:

public class ExtendedWebClient : WebClient
{
    public CookieContainer CookieContainer { get; private set; }
 
    [SecuritySafeCritical]
    public ExtendedWebClient()
    {
        this.CookieContainer = new CookieContainer();
    }
 
    protected override WebRequest GetWebRequest(Uri address)
    {
        WebRequest request = base.GetWebRequest(address);
 
        if (request is HttpWebRequest)
            (request as HttpWebRequest).CookieContainer = this.CookieContainer;
 
        return request;
    }
}
Searching the network you can discover many examples of this technique applied to the .NET Framework WebClient but they does not works with the WebClient on the WindowsPhone. It is because for security purposes the WebClient constructor is decorated with the SecuritySafeCritical attribute, but is suffice you also specify the same attribute on the default constructor of the extended class and it starts working perfectly. Beautiful is that due to the inheritance rules you can exchange the basic WebClient with the ExtendedWebClient and your application will support cookies out of the box.

Conclusion

The code provided with this article has been tailored to show a real data access scenario using the WebClient class. In the following articles I will add new pages to show the other networking tools in your WindowsPhone. In this example I taken advantage of a session variable to retain a list of names. You can add or remove your names to it easily. The protocol is made of a simple xml document serialized and deserialized with XDocument.


Subscribe

Comments

  • ccatto

    Re: Windows Phone 7 Data Access Strategies: WebClient


    posted by ccatto on Aug 18, 2011 23:30
    Hey NowAndrea Boschin

     Nice post on Data w/ WP7!

  • JrmyCorpinot

    Re: Windows Phone 7 Data Access Strategies: WebClient


    posted by JrmyCorpinot on Apr 12, 2012 02:36

    Wow! Thank you! You just save me. I was going crazy, i couldn't find why this override of WebClient on WindowsPhone didn't work. It was keeping throw an exception.

    Your ExtendedWebClient works well :)

  • AndreaBoschin

    Re: Windows Phone 7 Data Access Strategies: WebClient


    posted by AndreaBoschin on Apr 12, 2012 03:13
    I'm happy to help!
  • BjrnHansson

    Re: Windows Phone 7 Data Access Strategies: WebClient


    posted by BjrnHansson on Jun 15, 2012 14:28

    I have problems with the implementing of getwebrequest method, it says i can't access the method because of the protection level. Could you in more detail describe how to use the method?

  • AndreaBoschin

    Re: Windows Phone 7 Data Access Strategies: WebClient


    posted by AndreaBoschin on Jun 15, 2012 16:24
    Have you tryied the attached sample?
  • joysika

    Re: Windows Phone 7 Data Access Strategies: WebClient


    posted by joysika on Sep 14, 2012 11:59
    Thank you, you saved me. Your solution is wonderful, work like a charm.
  • AdarshUrs

    Re: Windows Phone 7 Data Access Strategies: WebClient


    posted by AdarshUrs on Oct 26, 2012 12:28

    Hi Andrea Boschin

    Your article is cool, but one problem I want receive string from a website using IP address(as Uri), I don't know the IP address,  So I am giving uri like this

    string IP = "10.0.0."

    isIPFound = false;

    GetStatus(){

     for (int i = 0; i < 20; i++)
               {
                   uri = IP + i.ToString()+ "path";                

     client.DownloadStringAsync(new Uri(uri));                

     client.DownloadStringCompleted += newDownloadStringCompletedEventHandler(client_downloadStringCompleted);

     if (isIPFound)
                   {

                      isIPFound= false;
                       break;
                   }

    }


    client_downloadStringCompleted(s,e)

    {

    if (e.Error == null)
                {
                    isIPFound = true;
                }

    }


    What I want is, It should keep changing the ip address untill maximum limit if ip found within the limit It should stop the operation and return the ip address.

    I am a student, working on my first windows phone app help me,

    Thanks


      


  • iceman198

    Re: Windows Phone 7 Data Access Strategies: WebClient


    posted by iceman198 on Nov 02, 2012 15:26

    Hello,

    I'm trying to implement this (similar anyways) with google...and need to let google store cookies...but for some reason it is not jumping to the derived class (in your case 'ExtendedWebClient()') to allow cookies...am I missing something?  I'm doing it very similar to you...my code is:

    public WebClient InnerWebClient { get; set; }
    //.....//
    public MyWebClient()
    {
    this.InnerWebClient = new CookieWebClient();
    }
    //.....//
    this.InnerWebClient.UploadStringAsync(new Uri(url), verb, "");
    this.InnerWebClient.UploadStringCompleted += (sender, e) =>
    {
    System.Diagnostics.Debug.WriteLine("Results from our webclient UPLOAD request! // " + e.Result.ToString());
    resultPreExecuteLogin = new Result(e.Result.ToString());
    };
    //.....//
    Here is my derived class....
    public class CookieWebClient : WebClient
    {
    public CookieContainer CookieContainer { get; private set; }

    [SecuritySafeCritical]
    public CookieWebClient()
    {
    this.CookieContainer = new CookieContainer();
    }

    protected override WebRequest GetWebRequest(Uri address)
    {
    WebRequest request = base.GetWebRequest(address);

    if (request is HttpWebRequest)
    (request as HttpWebRequest).CookieContainer = this.CookieContainer;

    return request;
    }
    }

    If you have any ideas, they would be greatly appreciated.  I've been banging my head on this for days now.

    Thanks.

Add Comment

Login to comment:
  *      *       

From this series