Making Silverlight Emulate Synchronous Requests

How to make Silverlight emulate synchronous web requests and posts.

Before I start, I just want to mention that this is a very old "flame war". On the official Silverlight Forums, there was a thread that went on and on, becoming quite ugly with various ad-hominem attacks, and it eventually got so ugly that --thankfully -- the moderators took the whole thread down. However, not being averse to controversy, I of course had my say there and was duly accused by various characters of being arrogant, an idiot, and what not. My position then was exactly as it remains now; basically, Microsoft is not going to give you native synchronous web requests in Silverlight because there is too much room for abuse by developers who will freeze the UI Thread, the browser then locks up, and of course Microsoft gets blamed for a "crappy product".

Furthermore, in order to remain truly "cross browser", Silverlight needs to use the NPAPI architecture that almost all modern browsers provide. This only allows for asynchronous web requests.

Flash developers, having had to use async-only for a lot longer than Silverlight developers, have exactly the same situation except that they are by now a bit more gentlemanly about accepting it. The advice I gave then (and now) was, "Get over it."

However any discussion of this situation would not be complete without mention of different ways that you actually can "fake" the async runtime into "emulating" a synchronous call. My position on this, and the way I've been programming Silverlight from the start, is not to use this - instead, go with the flow, learn to use and take advantage of the async model, and 99% of what you need to do will be eminently do-able. The other 1% might even be the result of poor design by -- gasp -- you, the developer!

There have been a number of successful attempts by various developers to "fake" a synchronous Web Request, some of them extremely complex and unwieldy to use, but what I'll illustrate here is actually very simple - both to understand, and to use.

I'll give you an example of both a GET and a POST using the handy WebClient Class. For the GET, we'll just retrieve an RSS feed and display it in a TextBlock. I didn't even bother formatting it from the raw XML as that is not the purpose of the demo code.

For the POST, we'll post a string to an ASPX page on localhost, take the posted string, prepend "You posted:" to it, and send it back to the client for display. You can use the same technique for posting XML to a WebService or to a REST-enabled service endpoint.

And finally, though I do not show it here, the download contains a complete implementation of the same technique doing multiple items in parallel using the Silverlight Threadpool.

That should cover 90 percent or better of all the developer scenarios you may run into. Once you see the simple technique I show, it should be pretty easy to adapt it to HttpWebRequest / Response or any other call you need to make.

The class that handles the "heavy lifting" for both the GET and the POST is pretty simple:

namespace SyncWebClient
{
public static class SyncWc
{

private static ManualResetEvent mre = new ManualResetEvent(false);
public static string GetData(string url)
{
string s = "";
WebClient wc = new WebClient();
wc.
DownloadStringCompleted += (sender, e) =>
{
s = e.
Result;
mre.Set();
};
wc.
DownloadStringAsync(new Uri(url));
mre.
WaitOne(1000);
return s;
}

public static string PostData(string url, string dataToPost)
{
string s = "";
WebClient wc = new WebClient();
wc.
UploadStringCompleted += (sender, e) =>
{
s = e.
Result;
mre.Set();
};
wc.
UploadStringAsync(new Uri(url),dataToPost);
mre.
WaitOne(1000);
return s;


}
}
}

So when you want to do a GET Request for the results of a Uri, you would use code like this:

private void Button1_Click(object sender, RoutedEventArgs e)
{
Thread thd = new Thread(new ParameterizedThreadStart(GetPage));
thd.
IsBackground = true;
thd.
Start("http://feeds.feedburner.com/blogspot/lGrQ");

}

private void GetPage(object url)
{
string s= SyncWc.GetData((string)url);
Dispatcher.BeginInvoke(() => text1.Text = s);
}



To make a POST, the code would resemble the following:

private void Button2_Click(object sender, RoutedEventArgs e)
{
Thread thd = new Thread(new ParameterizedThreadStart(PostPage));
thd.
IsBackground = true;
//format the 2 items needed as a semicolon-delimited string so you can split to an array for use.
thd.
Start("http://localhost:2067/PostToMe.aspx;this is the data to post");
}

private void PostPage(object stuff)
{
Dispatcher.BeginInvoke(() => text1.Text = "");
string[] guys = ((string) stuff).Split(";".ToCharArray() );
string url = guys[0];
string data = guys[1];
string s = SyncWc.PostData((string)url,data);
Dispatcher.BeginInvoke(() => text1.Text = s);
}


The "PostToMe.aspx" page just does this:

protected void Page_Load(object sender, EventArgs e)
{
byte[] bData = Request.BinaryRead(Request.ContentLength);
string s = System.Text.Encoding.UTF8.GetString(bData);
string returnString = "You posted:" + s;
Response.Write(returnString);
}


Note in all cases that using the timeout overload on the ManualResetEvent's WaitOne method is important. If something bad happens, you "DO" want your call to timeout and return, otherwise you have simply accomplished exactly what Microsoft is trying to avoid - locking up the UI thread.

All you are doing is making your call on a background thread, and using the ManualResetEvent's WaitOne call to "hold up the operation" until either your result is ready to send back or the operation times out. In either case, it looks and acts "just like" a synchronous, blocking method call. The rest of the code is simply required Dispatcher code to update the UI from a background thread. Again, the reason why this approach does not interfere with the UI thread is because it is not being done there - it's being done on a background thread.

If you want to get more sophisticated, Silverlight supports the ThreadPool where you can use QueueUserWorkItem to do the same thing and achieve a degree of parallelization with multiple requests. You can use a counter variable to tell when to call the ManualResetEvent's Set method after all of the requests have completed, and even aggregate the results as if it were a single blocking call. Or, you could use the WaitHandle.WaitAll method overloads with an array of ManualResetEvent, which is what I've used in the download. Just don't do it on the UI thread.

You can download the full working Visual Studio 2008 sample solution here. The download includes a full implementation of doing the operation with multiple items using the Silverlight Threadpool with WaitHandle.WaitAll.

By Peter Bromberg   Popularity  (7725 Views)