Introduction
Silverlight gives users the ability to create an extremely rich UI experience for the user, but what about the server? How can I take advantage of Silverlight to do simple access to a server whether it's a LINUX Web Server or a Windows Web Server? What if I want to make REST calls to my server? Silverlight includes a few namespaces for doing simple request and response to and from the server: System.Net and System.Net.Browser. These assemblies contain the following classes that will allow us to talk to our server over the web:
Class | Description |
WebRequestCreator | Allows us to create a web request given a URI |
HttpWebRequest | A web request in which we can set our request |
HttpWebResponse | The web response coming back from the server |
With the help of just these three classes, we can do everything we need to do to push data from the Silverlight client to the web server and retrieve information from the web server into our Silverlight client.
The Client Access Policy
In order to have authorization to use Silverlight against the web server, the web server must have a client access policy file installed in the root. Without this policy in place on the web server, Silverlight will continue to throw authorization exceptions anytime it tries to contact a server on another domain with the classes listed above. Listing 1 shows a typical client access policy. This policy allows all headers to go through on a request and allows requests from all domains. It also allows you to use any of the http methods: GET, PUT, POST, and delete. You can restrict any of the access by simply replacing the wildcard asterisk in each tag with a more specific value.
Listing 1 - Typical Client Access Policy
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
<cross-domain-access>
<!--Enables Silverlight 3 all methods functionality-->
<policy>
<allow-from http-methods="*" http-request-headers="*">
<domain uri="*"/>
</allow-from>
<grant-to>
<resource path="/resources" include-subpaths="true"/>
</grant-to>
</policy>
<!--Enables Silverlight 2 clients to continue to work normally -->
<policy>
<allow-from >
<domain uri="*"/>
</allow-from>
<grant-to>
<resource path="/api" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
Now that we got that out of the way, we can do some real work on the client. Let's start with a simple http GET method. Everything done in Silverlight is asynchronous. Initially the asynchronous world is a difficult planet to live on, but eventually you start to get the hang of residing on it and may even enjoy it. To implement a GET we first create a web request and then call it. The web request is created with the the convenient WebRequestCreator. You can think of the WebRequestCreator as a factory that churns out HttpWebRequest objects. Just feed it a uri and out spills a web request. Keep in mind that you still need to populate the request with the methods, headers, and data. Once you have your web request ready, just call the asynchronous BeginGetResponse and wait. Listing 2 shows an example of creating the request and making the asynchronous call.
Listing 2 - Calling BeginGetResponse
var request = WebRequestCreator.ClientHttp.Create(uri)
as HttpWebRequest;
request.Method = "GET";
request.Headers[AUTHORIZATION_HEADER] = token;
request.BeginGetResponse(onGetResponse, request);
How long do we wait? We don't care, because we send the BeginGetResponse a callback that the Silverlight framework will call when it is good and ready. The callback has the signature MyMethod(IAsyncResult asyncResult). You can pass in your own delegate into BeginGetResponse with this signature and handle the response when Silverlight calls the delegate.
In listing 3, is our callback onGetResponse that we passed into BeginGetResponse. Inside of this method, we can extract our response from the IAsyncResult by calling EndGetResponse. Calling EndGetResponse retrieves the HttpWebResponse for us.
private string onGetResponse (IAsyncResult asyncResult)
{
var request = asyncResult.AsyncState as HttpWebRequest;
var response = request.EndGetResponse(asyncResult)
as HttpWebResponse;
var reader = new StreamReader(response.GetResponseStream());
string
responseBody = reader.ReadToEnd();
}
One thing I noticed while working with Silverlight 3 is that your response never really comes back until you actually call EndGetResponse. This is not the behavior I would have expected, because if you ever wanted to block after BeginGetResponse, you can't because you'd never get the response. Not sure if this is what Microsoft intended, but I haven't seen this behavior in other technologies. Maybe it has changed in Silverlight 4? In any case, once your call EndGetResponse to get the response, you can read the results of the body by retrieving the response stream. For something like reading json from the body, you can simply use a StreamReader since for all intents and purposes json is a string.
Calling Multiple Gets
What if I wanted to do something a bit more interesting? What if I wanted to chain multiple gets together and produce a list of responses? In a synchronous world this would just require calling a for loop around each GET, but in an asynchronous world, this will not really work, especially if you want to be able to control the order of the results coming back. So what we would need to do in the asynchronous world, is wait for the response to come back for each http GET in the callback, and then fire off the next http GET inside the callback. Listing 4 illustrates this technique of batching http GETS. The method takes a list of uris representing the different GET requests. It uses a recursive call to call back on itself with the next uri (http GET request) every time the callback is called. Each time a new call is made, the uri is removed from the uri list and the next result is added to the list of results . When the uri list becomes empty, we make the final callback to the silverlight application.
Listing 3 - Batching Multiple Gets Asynchronously.
protected void BatchingMultipleGetMethods(List<Uri> uris, IList<JsonObject> results, ActionCallback<IList<JsonObject>> onFinalCallback)
{
if (uris.Count > 0)
{
var request = WebRequestCreator.ClientHttp.Create(uris[0]) as HttpWebRequest;
request.Method = "GET";
request.BeginGetResponse(x
=>
{
var
response = GetResponseBody(x);
results.Add(JsonValue.Parse(response)
as JsonObject);
if (uris.Count > 0)
{
uris.RemoveAt(0);
ExecuteMultipleGetMethods(uris,
results, onFinalCallback);
}
},
request);
}
else
{
onFinalCallback(new CallbackResult<IList<JsonObject>>(results));
}
}
protected string GetResponseBody(IAsyncResult asyncResult)
{
try
{
var request
= asyncResult.AsyncState as HttpWebRequest;
var response = request.EndGetResponse(asyncResult) as HttpWebResponse;
var reader = new StreamReader(response.GetResponseStream());
return reader.ReadToEnd();
}
catch (Exception
ex)
{
return "{\"ERROR\":\"" +
ex.Message + "\"}";
}
}
Posting Data In Silverlight
Posting data to the server via http is a bit more convoluted. It is more of a three step process. To post data, we use the BeginGetRequestStream method on the HttpWebRequest class. Listing 4 shows how we wrap the request with the data in a PostData class that we create. Inside the post wrapper we include the request, the data, and the callback we wish to return to the user. Inside the callback CreatePostCallback, we call EndGetRequestStream, write the data to the stream, and then kick off the BeginGetResponse. Then in the response callback (OnFinalCallback), passed from the BeginGetResponse call, we unwrap the post wrapper and call EndGetResponse to get the response from the post. Confusing? Yeah, that's what I thought. It would be a lot simpler to just execute a post with the data and retrieve the data in the response, but unfortunately, this is not the case in Silverlight. The ExecutePostMethod listing 4 simplifies your life by wrapping all the complicated mess.
Listing 4 - Implementing an Http Post in Silverlight
private void ExecutePostMethod(Uri uri, JsonObject
json, Action<string>
onGetResponse)
{
var request = WebRequestCreator.ClientHttp.Create(uri)
as HttpWebRequest;
request.Method = "POST";
request.ContentType
= "text/json";
var res = new PostData <string>()
{
Request =
request,
Data =
json.ToString(),
ResponseCallback
= onGetResponse
};
request.BeginGetRequestStream(CreatePostCallback,
res);
}
private void CreatePostCallback(IAsyncResult ar)
{
var requestData = ar.AsyncState as PostData <string>;
var request = requestData.ContextCall;
Stream requestStream =
request.EndGetRequestStream(ar);
StreamWriter sw = new StreamWriter(requestStream);
sw.Write(requestData.Data);
sw.Flush();
requestStream.Close();
request.BeginGetResponse(OnFinalCallback,
requestData);
}
private static void OnFinalCallback (IAsyncResult
result)
{
var userStateWrapper = result.AsyncState as PostData <string>;
string ret;
try
{
var resp = (HttpWebResponse)userStateWrapper.
Request.EndGetResponse(result);
var streamReader = new
StreamReader(resp.GetResponseStream());
string resultString = streamReader.ReadToEnd();
var jObj = JsonObject.Parse(resultString)
as JsonObject;
ret =
resultString;
}
catch (Exception ex)
{
ret = "{\"ERROR\":\"" +
ex.Message + "\"}";
}
userStateWrapper.ResponseCallback
(ret);
}
public class PostData<T>
{
public HttpWebRequest
Request { get; set;
}
public string Data { get; set; }
public Action<T>
ResponseCallback { get; set; }
}
Conclusion
Entering the asynchronous world of Silverlight when talking to web servers can be quite an adventure, so it can be useful to create helpers that wrap the common http requests. This article has shown you how you can wrap two of the 4 methods: GET and POST. It turns out your implement PUT, very similarly to how you would implement a POST and you would implement an http DELETE very similarly to how you would implement a GET. You just need to change the method string in request.Method to the appropriate method name.
Note that this article is particularly applicable to ASP.NET MVC , because you can use the silverlight requests to make REST calls to your Controller methods inside of your Windows Web Server assemblies.
Anyway, have fun using Silverlight to talk to the web world. Sit back, take a REST, and enjoy the window to the world that Silverlight opens up to you on the internet using C# and .NET.