Notes from Silverlight unit testing with the ESRI Silverlight API
I have been using the ‘Silverlight Unit Test Framework’ to do unit testing for our Silverlight applications. Unit testing applications using the ESRI Silverlight API throws a monkey wrench into the Silverlight unit testing process. The Silverlight unit testing libraries can normally be run with the automatically generate HTML page option in VisualStudio 2008. But unit tests with the ESRI Silverlight API map control will not run on the auto-generated HTML pages since the map control cannot load its layers when run from a web page on disk. So, to test the mapping applications, the unit tests should be run with a web application project which hosts the page over HTTP.
Well, that wasn’t a very painful solution was it? But the pain enters the picture when we have to run these Silverlight mapping unit tests on the build server. We don’t necessarily want to publish a web application just to run the Silverlight mapping unit tests. Ideally, we would just like to run the unit tests in the XAP file by themselves without publishing a web application with IIS on the build machine. In fact, our build machine does not run IIS. the ‘Silverlight Unit Test Framework’ doesn’t include any built-in support for such use cases.
But we can solve the problem above using the Statlight Silverlight testing automation tool. Statlight also provides support for integration with TeamCity, our build server of choice. The Statlight tool runs the test library XAP file over HTTP using a web server that it hosts when running. The tool is pretty simple to use and more details can be found at the ‘Getting Started’ page. I did run into some issues with running my tests over Statlight because of the fact that the Statlight tool actually creates it own XAP file when running the tests and copies over only the DLLs from the XAP file being tested. I had made use of some ‘Content’ files in my unit tests that didn’t get copied over and caused the issue. But I was able to solve the problem easily by switching the ‘Content’ files into embedded file in the DLL. Statlight’s creator was also kind enough to give me more information about the inner workings of the Statlight tool. Here it goes
- Creates a webserver in the background. (hosted in the console)
- Creates a browser in the background. (WinForm thread with a WebBrowser control)
- The browser loads up StatLight silverlight application. (http://localhost:<port discovered>/…)
- The silverlight application connects to the webserver and retrieves the xap to test via REST.
- The SL app loads and exectues the tests in the xap.
- The SL app reports results back to the webserver via WCF.
- The webserver reports results back to the Console / CI server.
So, with Statlight, we can avoid unnecessary complexities with running Silverlight unit tests on the build server and report Silverlight mapping unit test results with the rest of the test results. If you are looking for solutions to running your Silverlight unit tests on your build server, i recommend looking into Statlight.
Making HTTP Post with Silverlight a little easier
In my earlier post, I talked about how to go about making an HTTP Post in Silverlight. The whole process is a little complicated to say the least. Silverlight does not come with an utility class like WebClient that makes making a HTTP Get a little easier. So, I created a class called ‘HTTPPostHelper’ that does for HTTP Post what WebClient does for HTTP Get. The ‘HTTPPostHelper’ class listed below, makes the whole process a lot easier. To use the class, do the following
- Create an instance of the HTTPPostHelper class with the type of the input class (T) and the type of the returned class (K) as the generic arguments.
- Listen to the ‘PostCompleted’ event that gets called when the Post operation completes successfully. The EventArgs of the event contains an instance of the return class type.
- Listen to the ‘PostFailed’ event that gets called when the Post operation fails. The EventArgs of the event contains the exception information.
- Call the ‘Post’ method on the HTTPPostHelper with the input object and the URL address to make the Post operation happen.
- The input object gets serialized by default using the DataContractSerializer. But this can be overridden by setting the Serializer property of the HTTPPostHelper with an appropriate delagate that serializes the input object to a stream.
- The output from the HTTP Post operation is deserialized by default using the DataContractSerializer. But this too can be overridden by the Deserializer property of the HTTPPostHelper.
- For example, if you want to send and receive JSON data to the HTTP endpoint, just override the default Serializer & Deserializer with delegates that use DataContractJsonSerializer instead or your own custom binary serializer if need be.
- The ‘ContentType’ used for the Post operation can also set before calling the Post method.
- The class handles all the thread hopping internally. The ‘PostCompleted’ and the ‘PostFailed’ events get raised on the same thread from which they were called from.
public class HTTPPostHelper<T, K>
{
SynchronizationContext syncContext;
HttpWebRequest _request;
public string ContentType
{
get;
set;
}
public event EventHandler<EventArgs<K>> PostCompleted;
public event EventHandler<ExceptionEventArgs> PostFailed;
public Action<T, Stream> Serializer = null;
public Func<Stream, K> Deserializer = null;
public HTTPPostHelper()
{
Serializer = DefaultSerializer;
Deserializer = DefaultDeserializer;
ContentType = “application/stream”;
}
void DefaultSerializer(T input, Stream output)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(output, input);
output.Flush();
}
K DefaultDeserializer(Stream input)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(K));
K result = (K)serializer.ReadObject(input);
return result;
}
public void Post(Uri address, T input)
{
syncContext = SynchronizationContext.Current;
_request = (HttpWebRequest)WebRequest.Create(address);
_request.Method = “POST”;
_request.ContentType = “application/stream”;
_request.BeginGetRequestStream(new AsyncCallback(PostRequestReady), new HttpWebRequestData<T>()
{
Request = _request,
Data = input
});
}
private void PostRequestReady(IAsyncResult asyncResult)
{
HttpWebRequestData<T> requestData = asyncResult.AsyncState as HttpWebRequestData<T>;
HttpWebRequest request = requestData.Request;
Stream requestStream = request.EndGetRequestStream(asyncResult);
Serializer(requestData.Data, requestStream);
requestStream.Close();
request.BeginGetResponse(new AsyncCallback(PostResponseCallback), request);
}
private void PostResponseCallback(IAsyncResult ar)
{
try
{
HttpWebRequest request = ar.AsyncState as HttpWebRequest;
HttpWebResponse response = request.EndGetResponse(ar) as HttpWebResponse;
Stream responseStream = response.GetResponseStream();
K urls = DefaultDeserializer(responseStream);
syncContext.Post(PostExtractResponse, new HttpWebResponseData<K>()
{
Response = response,
Data = urls
});
}
catch (Exception ex)
{
syncContext.Post(RaiseException, ex);
}
}
private void RaiseException(object state)
{
Exception ex = state as Exception;
if (PostFailed != null)
PostFailed(this, new ExceptionEventArgs(ex));
}
private void PostExtractResponse(object state)
{
HttpWebResponseData<K> responseData = state as HttpWebResponseData<K>;
if (PostCompleted != null)
PostCompleted(this, new EventArgs<K>(responseData.Data));
}
}
public class ExceptionEventArgs : EventArgs
{
public Exception ExceptionInfo { get; set; }
public ExceptionEventArgs(Exception ex)
{
ExceptionInfo = ex;
}
}
public class EventArgs<T> : EventArgs
{
private T eventData;
public EventArgs(T eventData)
{
this.eventData = eventData;
}
public T EventData
{
get
{
return eventData;
}
}
}
Making a HTTP Post in Silverlight a.k.a thread hopping
Making a HTTP GET request is as easy as pie using the WebClient class that is available in the Silverlight library. The DownloadStringCompleted event callback of the WebClient class is invoked on the main UI thread thus allowing the developer to access UI elements in the callback method itself. If the web service call resulted in a failure, then that condition can be easily determined in the DownloadStringCompleted event callback method by checking if the Error property on the DownloadStringCompletedEventArgs argument passed into the callback method is NULL. The code snippet below illustrates one way to use the WebClient class.
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += (sender, e) =>
{
if (e.Error == null)
{
//Process web service result here
txtBox.Text = e.Result;
} else
{
//Process web service failure here
txtBox.Text = e.Error.Message;
} callback();
};
webClient.DownloadStringAsync( new Uri( “URL address that needs to be fetched” ) );
But when it comes to making a HTTP POST in Silverlight, things aren’t so simple. Silverlight doesn’t come with any helper classes like the WebClient (for making HTTP GET requests) to help take the hassle out of making a HTTP POST. So, to make a HTTP POST request, we must jump through two threads before we get back on the main UI thread where we will be able to access the UI elements and update them as needed. Here is a simple illustration of the process that we have to go through to make a HTTP POST.
Before we get into code snippets for actually accomplishing the above, let’s get into a little bit of why the above needs to happen. When the user initiates an UI event (button click etc) upon which we must make the HTTP request, we need to ask Silverlight/browser to open a HTTP request pipe to the specified URL. We get this request stream in a different non-UI thread. The reason for this being that Silverlight makes HTTP requests through the browser stack (Silverlight 3 can use its own HTTP stack if needed, but uses the browser stack by default). As you might know, browsers limit the number of outgoing HTTP requests to a given domain name. This limit used to be 2 but modern day browsers have increased this limit (8 in IE8 etc). So, given this limit on the outgoing HTTP requests, there is no guarantee that the browser can open a HTTP request pipe to the URL when demanded by Silverlight. So, we end up getting the request stream in a non-UI thread. We can write/post the necessary data to this request pipe and wait to get a response from the web service. The results from the web service is received on another different non-UI thread. Once we read all the information from the response stream, we can’t start updating the UI elements since we are still on a non-UI thread. So, we need to pass this response from the web service back to the UI thread before we can start updating the UI elements.
Since, we need to pass data between threads as explained above, I had written these tiny little classes to help us through the process of making the HTTP POST request.
public class HttpWebResponseData<T>
{
public HttpWebResponse Response { get; set; }
public T Data { get; set; }
}
public class HttpWebRequestData<T>
{
public HttpWebRequest Request { get; set; }
public T Data { get; set; }
}
public class ExceptionEventArgs : EventArgs
{
public Exception ExceptionInfo { get; set; }
public ExceptionEventArgs(Exception ex)
{
ExceptionInfo = ex;
}
}
public class EventArgs<T> : EventArgs
{
private T eventData;
public EventArgs(T eventData)
{
this.eventData = eventData;
}
public T EventData
{
get
{
return eventData;
}
}
}
We need to store away the SynchronizationContext of the UI thread before we begin making the HTTP POST request and start jumping threads. This is so that we can get back on the UI thread when the HTTP POST web service call has returned. So, create a variable to hold the in the SynchronizationContext Silverlight UserControl.
SynchronizationContext syncContext;
The following code snippet illustrates how to use the HTTPWebRequest and the HTTPWebResponse classes in Silverlight to perform the HTTP POST.
//This method will called on user action (button click etc); We are on the UI thread
private void DoHTTPPost(List<string> stuffToPost)
{
//Obtain and store away the SynchronizationContext of the UI thread in the private variable declared above
syncContext = SynchronizationContext.Current;
HttpWebRequest request = ( HttpWebRequest ) WebRequest.Create( “URL address to POST to” ) );
request.Method = “POST”;
request.ContentType = “text/xml”;
request.BeginGetRequestStream( new AsyncCallback( DoHTTPPostRequestReady ), new HttpWebRequestData<List<string>>()
{
Request = request,
Data = stuffToPost
} );
}
//This method will be called on the non-UI request thread
private void DoHTTPPostRequestReady(IAsyncResult asyncResult)
{
HttpWebRequestData<List<string>> requestData = asyncResult.AsyncState as HttpWebRequestData<List<string>>;
HttpWebRequest request = requestData.Request;
Stream requestStream = request.EndGetRequestStream(asyncResult);
DataContractSerializer serializer = new DataContractSerializer(typeof(List<string>));
//Write the string list as XML to the request stream
StreamWriter sw = new StreamWriter(requestStream);
serializer.WriteObject(requestStream, requestData.Data);
sw.Flush();
requestStream.Close();
request.BeginGetResponse(new AsyncCallback(DoHTTPPostResponseCallback), request);
}
//This methods will be called on the non-UI response thread
private void DoHTTPPostResponseCallback(IAsyncResult ar)
{
//Ignoring error handling here to keep things simple
HttpWebRequest request = ar.AsyncState as HttpWebRequest;
HttpWebResponse response = request.EndGetResponse(ar) as HttpWebResponse;
Stream responseStream = response.GetResponseStream();
DataContractSerializer serializer = new DataContractSerializer(typeof(string));
string result = serializer.ReadObject(responseStream) as string;
//Post the response from the web service back to the UI thread using the SynchronizationContext obtained when initiating the HTTP POST request
syncContext.Post(DoHTTPPostExtractResponse, new HttpWebResponseData<string>()
{
Response = response,
Data = result
});
}
//We are back on the UI thread when this method is called
private void DoHTTPPostExtractResponse(object state)
{
string response = (state as HttpWebResponseData<string>).Data;
//We can update the UI elements with the response here since we are back on the UI thread
txtBox.Text = response;
}
I will be posting more helper methods for the above pretty soon. Also, I will delve more into making HTTP POST requests to WCF REST endpoints in future posts.
GeoJSON & GPX support added to the ESRI Silverlight API Contrib
I’ve added support for the GeoJSON and GPX layer types to the ESRI Silverlight API Contrib project on CodePlex. Adding GeoJSON or GPX layer to the ESRI Silverlight API map is real easy and you would add it just like adding a FeatureLayer to the map.
Adding a GeoJSON layer to the map
<esri:Map x:Name="MyMap" Grid.Row="1">
<esri:Map.Layers>
<esri:ArcGISTiledMapServiceLayer ID="StreetMapLayer"
Url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer"/>
<slContrib:GeoJSONLayer ID="geoJSONLayer"
URL="http://localhost/SLMaps.Web/Data/JSON.txt" />
</esri:Map.Layers>
</esri:Map>
Adding a GPX layer to the map
<esri:Map x:Name="MyMap" Grid.Row="1">
<esri:Map.Layers>
<esri:ArcGISTiledMapServiceLayer ID="StreetMapLayer"
Url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer"/>
<slContrib:GPXLayer ID="gpxLayer"
URL="http://localhost/SLMaps.Web/Data/fells_loop.txt" />
</esri:Map.Layers>
</esri:Map>
This adds to the GeoRSS layer support already present in the library.
Other than just adding these data sources as layers to the map, The GeoJSONReader & GPXReader classes can also be used to just parse the data source and obtain the geometry and attribute values of the features in the data source if/as required. This should allow for the users to upload GeoJSON & GPX data from their GPS units directly from their computers to the Silverlight map control without the need to upload files to the server. Allowing the workflow to be smoother, faster and simple both on the server-side & client-side.
Bing map issues with IE6 & Silverlight 2 / ESRI Silverlight API 1.0
I have been having troubles for a while now getting the Bing map layers to show in IE6 with the Silverlight 2 installed. Unfortunately, when I got down to the bottom of it, it turned out to be a bug in Silverlight 2 that actually prevents the Bing map layers to show in the ESRI Silverlight API 1.0 map control. Initially, I wasn’t sure why the bing map layers where not showing up on just the IE6 browsers but displayed and worked just fine on IE7+ and Firefox. To check the issue for yourself, if you are using an IE6 browser with the Silverlight 2 plugin, just browse over the Bing maps demo site setup by ESRI to check it out for yourself. you will see the error message shown below.
Part of the error message displayed above in the alert window (“Make sure you are generating a VE token from the same token service as the VE Layer ServerType: Staging or Production”) is actually not accurate. The demo site simply handles the ‘InitializationFailed‘ event on the Bing TileLayer and displays the message above not matter what the error actually was. This had mislead me initially and had sent me hunting for clues in the wrong direction. One other weird I noted here in the ESRI Silverlight API 1.0 is that the event arguments for the ‘InitializationFailed‘ actually doesn’t provide you with the error information. But the error information is actually available in the ‘InitializationFailure‘ property of the layer which I thought was a little confusing. Hoping this will change in the next versions of the API.
After spending a lot of time on fiddler, snooping around the requests made to the Bing servers & searching the web, I finally figured out the reason why the Bing layers do not show up on the map. The reason turned out to be a bug in the Silverlight 2 plug-in in IE6. The error had to do with Silverlight 2 plug-in in IE6 having troubles handling compressed content returned from the server. More details about the bug can be found here. Since the compressed content in this case comes from the Bing servers, I had not control over it at all and thus it eventually turned out that the problem couldn’t be solved in a good way. But there was an advanced setting in IE6 that I could turn off to get the bing layers to show up in the map. Under the ‘Tools’ menu in IE6, select the ‘Internet Options’ item. In the ‘Internet Options’ dialog, click on the ‘Advanced’ tab and in the settings disable the ‘Use HTTP 1.1’ option.
But this workaround does have its own drawbacks of not being able to use some of the advanced HTTP 1.1 capabilities like compression etc. So, please be mindful of it before adopting the solution.
Some salvaging facts,
- This bug has been fixed in Silverlight 3. So, users who are still using IE6 but have upgraded to the Silverlight 3 plug-in will not have this problem.
- IE6 is losing market share – (though a good chunk of the users on the web still use IE6)
- The Silverlight 2 plug-in is no longer available for download from Microsoft.
- Windows update upgrades user with Silverlight 2 to Silverlight 3.
But, if you are writing a public application using the ESRI Silverlight API 1.0 that needs to support Silverlight 2 plug-in on IE6, you definitely might want to consider the above issue and display a message to you users asking them to upgrade or turn off the ‘Use HTTP 1.1’ option in IE6.
Hunting down memory leaks in Silverlight
Recently, I was noticing some memory issues with a Silverlight application I was working on. I was creating a dialog window (user control) dynamically where the user could edit and save data. The dialog user control was data bound to a ‘ViewModel’ type which populates the data controls in the dialog and is then used to populate and save a business object. After, the user clicks on the OK or the Cancel button, i was disposing away with the user control from memory by setting its reference to NULL. But, when i repeated the process of opening and closing the dialog multiple times, the memory used by Internet Explorer always kept going up. This made me suspicious that even though I was setting the user control’s reference to NULL, the user control was never disposed away from memory.
To confirm that multiple instances of the user control were still in memory, I resorted to using “windbg”. Following the steps below, you can confirm and debug memory leaks in Silverlight and also in the .NET CLR.
- Download and install windbg from here.
- Run the Silverlight application and bring it to a state where you think a memory problem exists. In my case above, I had to open and close the suspect dialog multiple times.
- Run windbg and attach to the Internet Explorer process running your Silverlight application.
- Load the SOS debugging extension for Silverlight. This is installed with the Silverlight SDK on your system. The ‘SOS.dll’ is found under the Silverlight installation directory. For me it is at “C:\Program Files\Microsoft Silverlight\2.0.40115.0\sos.dll”. To load the SOS debugging extension execute the command “.load C:\Program Files\Microsoft Silverlight\2.0.40115.0\sos.dll” in the windbg window.
- Run the “!dumpheap -stat” command to view the objects in memory and the count of their instances.
- Run the “!dumpheap –stat –type [TypeName]” command to view the number of instances of the specified type in memory. In my case, I found that the number of instances of the dialog user control object in memory was always equal to the number of times I had opened and closed the dialog user control. This indicated that the user control object were not getting disposed from memory even when I had set its reference to NULL.
- Run the “!dumpheap –type [TypeName]” command to view all the memory addresses of the object’s instances.
- Run the “!gcroot [Address]” command to view how the instance of the object can be reached. In other words, it tells us what is holding on to the reference of the object and thus keeping the object in memory without being cleaned up by the garbage collector. The output here is hard to read, but the pain of going through this exercise will definitely pay off when the memory leak in the application has been fixed.
In my case, it turned out that the ‘ViewModel’ object the dialog user control was being data bound to was holding on to a reference to the user control object. So, to fix the memory leak, all I had to do was to set the ‘DataContext’ property of the user control to NULL before setting the user control reference itself to NULL.
Flashing a graphic in the ESRI Silverlight API
Being able to flash a shape on the map is a nice usability feature in GIS applications to relate shapes on the map to attributes in a data table or data grid. Also, most GIS users have gotten used to the feature in ArcMAP. I had written up an extension method to the Graphic class in the ESRI Silverlight API to be able to do this easily with a simple function call. Here is the code for the extension method.
public static void Flash( this Graphic graphic )
{
Flash( graphic, 200, 10 );
}
public static void Flash( this Graphic graphic, double milliseconds, int repeat )
{
int count = 1;
repeat = repeat * 2;
Symbol tempSymbol = graphic.Symbol;
Storyboard storyboard = new Storyboard();
storyboard.Duration = TimeSpan.FromMilliseconds( milliseconds );
graphic.Symbol = null;
storyboard.Completed += ( sender, e ) =>
{
if( count % 2 == 1 )
graphic.Symbol = tempSymbol;
else
graphic.Symbol = null;
if( count <= repeat )
storyboard.Begin();
count++;
};
storyboard.Begin();
}
After including the above extension method in your project just call the ‘Flash’ method on the graphic object
graphic.Flash();
Note that I am using the Storyboard as the timer which is preferred over of the DispatcherTimer.
This has also been added to the ESRI Silverlight API Contrib project here.
Silverlight databinding limitations
Target of databinding in Silverlight “HAS” to be a “FrameworkElement” and not just a “DependencyObject” like in WPF. I am not sure that there is a reason why databinding to “DependencyObject”s in Silverlight is explicitly omitted by Microsoft. Seems like it might be an oversight. But even after many people have complained about it in the forums, the databinding to “DependencyObject”s feature doesn’t seem to be included in the beta version of the latest Silverlight 3 library.
So, what does that mean"? Where does it really become a limitation? Well, it really does limit our options when trying to databind values to the “Transform” classes like the “RotateTransform”, “ScaleTransform”, “SkewTransform”, “TranslateTransform” etc. Let’s say that you are symbolizing your points as an Ellipse or a Rectangle, and you want to be able to size your symbols based on some value in the Attributes collection of your Graphic. The best way to do this would be to apply a ScaleTransform on the Ellipse or Rectangle (on the RenderTransform property) and databind the attribute values to the ScaleX and the ScaleY dependency properties of the ScaleTransform. But since the ScaleTransform is only a DependencyObject and not a FrameworkElement, we cannot bind to the ScaleX and ScaleY properties of the ScaleTransform. This scenario can easily be solved by binding to the Width and Height property of the Rectangle or Ellipse. But there are a lot of other scenarios that cannot be overcome as easily. Like for example, you will not be able to label along a line by databinding to a RotateTransform on a TextBlock for example. Also in the ESRI Silverlight API, we cannot apply a TextSymbol to a polyline or polygon geometry.
Lack of good transparency support in Silverlight and what it means to online mapping apps
The support for transparency in PNG images in Silverlight is to say the least really bad. Here is what the Microsoft docs says about PNG transparency support in Silverlight
Silverlight does not support all possible color depths that are included in the PNG specification. The following are the PNG color depths supported in Silverlight:
- Indexed color: 1-bit, 4-bit, or 8-bit color depth (per channel).
- Truecolor: 24-bit color depth, or 32-bit color depth (per channel) for truecolor plus alpha.
Notably, gray scale (with or without alpha), and 64-bit truecolor, are not supported in Silverlight.
Note that they don’t mention anything about the support for 1, 2, 4 and 8 bit PNGs. This creates some problems when using the ESRI Silverlight API and you might what to keep your eye out for it and pay good attention to how your map caches are being generated. Look at the results when I overlay ArcGIS Online’s Transportation layer on top of the Imagery layer in the ESRI Silverlight maps.
<esri:Map x:Name=“MyMap” Grid.Row=“1” Grid.Column=“2”>
<esri:Map.Layers>
<esri:ArcGISTiledMapServiceLayer
ID=“StreetMapLayer”
Url=“http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer”/>
<esri:ArcGISTiledMapServiceLayer
ID=“mapLayerTransportation”
</esri:Map.Layers>
</esri:Map>
And here it what the map look like. As you can see, the the tile areas outside the continental US are grayed out.
Also, check out this Silverlight forum post where Morten schools Microsoft about the issue mentioned above.
Generics support in XAML
Support for creating generic classes in XAML is still lacking. The only XAML element that lets you specify the generic type argument is only the root element of the user control. I couldn’t find any way to create an instance of a generic class in XAML yet. One had a class called “NameValue” in the project and wanted to be able to create an ObservableCollection<NameValue> in XAML. The only way I was able to do it in XAML was to create another empty class called NameValueCollection that inherited from ObservableCollection<NameValue> and then create an instance of the “NameValueCollection” in XAML instead.
public class NameValueCollection : ObservableCollection<NameValue>
{
}
I think one way generics could be supported in XAML is by creating new MarkupExtensions for that purpose.
3 comments