Silverlight Networking - Being Responsive to the WebRequest, a WebResponse

This is already my fourth article on Silverlight Networking, with only the Credentials in mind. Please read my other articles first:

In the last article I wrote about the WebRequest that gets translated from Silverlight to Javascript. In this article I will cover the converting of a Javascript WebResponse to a Silverlight WebResponse. Also the routing of the Response to the rightful listener is part of this article.

Building the Response

I created the following Silverlight class that can be used from Javascript. It's a class that represents a WebResponse and contains the Response variables. You can check for example the StatusCode property it should be "200" for a successful request.

1 [ScriptableType] 2 public class JavascriptWebResponse 3 { 4 public JavascriptWebResponse() 5 { 6 Headers = new Dictionary<string, string>(); 7 TimedOut = false; 8 Aborted = false; 9 } 10 11 [ScriptableMember] 12 public string Body { get; set; } 13 14 [ScriptableMember] 15 public string StatusCode { get; set; } 16 17 [ScriptableMember] 18 public string StatusText { get; set; } 19 20 [ScriptableMember] 21 public bool TimedOut { get; set; } 22 23 [ScriptableMember] 24 public bool Aborted { get; set; } 25 26 public IDictionary<string, string> Headers { get; set; } 27 28 [ScriptableMember] 29 public void SetHeaders(string headers) 30 { 31 string[] headersSplit = headers.Trim().Split('\n'); 32 foreach (string header in headersSplit) 33 { 34 string[] splitHeader = header.Split(':'); 35 Headers.Add(splitHeader[0].Trim(), splitHeader[1].Trim()); 36 } 37 } 38 }

And then I add an anonymous javascript function as a handler of the Completed event of the Request. This function should wrap the response in the JavascriptWebResponse object and call the ResponseComplete on the JavascriptBridge object which is responsible for distributing the response to a callback function.

1 request.add_completed( 2 (function(executor, eventArgs) 3 { 4 var silverlightPlugin = document.getElementById('silverlightControl'); 5 var webResponse = silverlightPlugin.content.services.createObject('JavascriptWebResponse'); 6 webResponse.Body = executor.get_responseData(); 7 webResponse.TimedOut = executor.get_timedOut(); 8 webResponse.Aborted = executor.get_aborted(); 9 webResponse.StatusCode = executor.get_statusCode(); 10 webResponse.StatusText = executor.get_statusText(); 11 webResponse.SetHeaders(executor.getAllResponseHeaders()); 12 silverlightPlugin.content.Bridge.ResponseComplete( 13 executor.get_webRequest().get_userContext(), 14 webResponse); 15 }) 16 ); 17

Associating the Response with a callback function

Because it is possible to run multiple requests at once that can differ to each other it's important to be able to identify every request. You can use the "userContext" on the javascript Sys.Net.WebRequest class to give it context.

request.set_userContext('fc7444e9-9f7c-4e79-9ed4-43f42ed38ba0');

And to read it back from the anonymous javascript function you can use:

executor.get_webRequest().get_userContext()

I make use of a Guid to generate an unique identification for every request. Besides this I have some code that accepts a function pointer and associates this function-pointer to the generated Guid.

1 private readonly Dictionary<string, EventHandler<JavascriptResponseEventArgs>> responseListeners = 2 new Dictionary<string, EventHandler<JavascriptResponseEventArgs>>(); 3 4 public string SubscribeToResponse(EventHandler<JavascriptResponseEventArgs> functionToCallOnResponse) 5 { 6 string listenerReference = Guid.NewGuid().ToString(); 7 lock (responseListeners) 8 { 9 responseListeners.Add(listenerReference, functionToCallOnResponse); 10 } 11 return listenerReference; 12 }

In Silverlight I have one method where all responses come in complete with the Guid. It looks up  a function-pointer and executes it.

1 [ScriptableMember] 2 public void ResponseComplete(string listenerReference, JavascriptWebResponse response) 3 { 4 EventHandler<JavascriptResponseEventArgs> functionToCallOnResponse = 5 responseListeners[listenerReference]; 6 if (functionToCallOnResponse != null) 7 functionToCallOnResponse(this, new JavascriptResponseEventArgs {WebResponse = response}); 8 //After the call the response is completed the function-pointer can be removed. 9 lock (responseListeners) 10 { 11 responseListeners.Remove(listenerReference); 12 } 13 } 14

Full usage of Request till Response

So we now have ourselves the ability to give a function-pointer to execute after the response is complete. The following example shows the full usage of request and response.

1 public void MakeRequest() 2 { 3 IWebRequestExecutor requestExecutor = new JavascriptWebRequestExecutor(); 4 5 var request = new JavascriptWebRequest 6 { 7 Uri = "http://localhost:2851/Test.xml", 8 Method = "GET" 9 }; 10 11 requestExecutor.Execute(request, OnResponse); 12 } 13 14 public void OnResponse(object sender, JavascriptResponseEventArgs e) 15 { 16 bool aborted = e.WebResponse.Aborted; 17 string body = e.WebResponse.Body; 18 IDictionary<string, string> headers = e.WebResponse.Headers; 19 string statusCode = e.WebResponse.StatusCode; 20 string statusText = e.WebResponse.StatusText; 21 bool timedOut = e.WebResponse.TimedOut; 22 }

What's next?

So we have ourselves everything from request to response, what's next? I already did some refactoring during the preparations of this article, and probably will do some more. Also I want to write some unit-tests to really test this framework before thinking about bringing it out as open-source. Before getting that far, there is still one bridge to cross. We have the trouble of cross-domain-scripting! Because Javascript won't let us execute requests outside of the current domain. I will cover a solution for this in my next article.

Gravatar