Silverlight Binary Serialization and Compression with WCF Services

WCF is a great tool, and because of the streamlined nature of Silverlight, it is virtually essential for various types of data access. We show how to do Silverlight Binary Serialization and Compression with WCF Services.

The default behavior of WCF is to serialize your types via the DataContractSerializer. If you have significant amounts of data going to the server (or, more likely) coming back from the server, that results in what can be a very large glop of textual XML going back and forth over the wire.

In the example case, to send 100 small “TestObject” class instances in a generic List occupies 60,804 bytes of glop going over the wire and a similar amount coming back. Multiply that by a large number of simultaneous clients and you should get the picture quite easily.

But what if we could do Binary Serialization (not XML) and compress the data, sending and receiving it as a simple byte array? With this approach, that 60,0000 + bytes is reduced to just 4,406 bytes. In other words, a 92.7 percent reduction in bandwidth!

It’s not exactly rocket science to figure out that as long as the amount of time required to serialize and compress or decompress and deserialize our payload is reasonable, we have a very significant bandwidth savings here.

That is what this sample application is all about. I will provide you with all the infrastructure you need to implement the technique; all you need to do is figure out what business logic you want to plug into it. And the nice thing about it is that you can implement it on a per-method basis, leaving other WCF methods to work the “old way”.

To do this, I used two classes:

QuickLZSharp by Lasse Mikkel Reinhold. This is a very fast compression class that ports to Silverlight easily. It achieves both speed and a high compression ratio.

Silverlight Serializer, a binary serialization class by Mike Talbot that does a great job of doing binary serialization on just about anything you want to throw at it, and of course was actually written to work in Silverlight.

NOTE: As of 9/12/2010 Mike has updated his serializer incorporating both my suggestions and those of others, so you may want to check his blog entry.

I also created a PayloadHelper class that makes it much easier to process your “Stuff”:

using System;
using System.Net;
using System.Collections.Generic;
namespace SLcompression
{
public static class PayloadHelper
{
public static byte[] ObjectToCompressedBytes ( object obj)
{
byte[] retval = null;
byte[] b = Serialization.SilverlightSerializer.Serialize(obj);
retval= QuickLZSharp.QuickLZ.compress(b);
return retval;
}

public static object CompressedBytesToObject( byte[] compressed)
{
object retval = null;
byte[] b = QuickLZSharp.QuickLZ.decompress(compressed);
retval=(List<TestObject>)Serialization.SilverlightSerializer.Deserialize(b);
return retval;
}
}
}

The names of the methods above should be self-explanatory. My sample WCF Service code looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using QuickLZSharp;
using Serialization;
using SLcompression;


namespace SLcompression.Web
{

[ServiceContract ]
public class Service1
{
[OperationContract]
public byte[] ProcessObjects (byte[] objects)
{
byte[] retval = null;

object o = PayloadHelper.CompressedBytesToObject(objects);

List<TestObject> list = (List<TestObject>) o;

// You can do whatever your business logic dictates here. You have a List<TestObject>
// you can stick it in a database, whatever and send back whatever type you want.
// Here, we're just modifying the objects in the list as a proof of concept,
// and sending them back to the Silverlight client.)
foreach (var t in list)
{
t.FirstName += ":done.";
}

retval = PayloadHelper.ObjectToCompressedBytes(list);
return retval;
}

[OperationContract]
public List<TestObject > ProcessObjectsOldWay (List<TestObject> objects)
{
foreach (var t in objects)
{
t.FirstName += ":done.";
}
return objects;
}
}
}

And the code in my Silverlight app looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using Serialization;
using SLcompression.ServiceReference1;
using SLcompression;

namespace SLcompression
{
public partial class MainPage : UserControl
{
private DateTime startTime;
private DateTime EndTime;

public MainPage()
{
InitializeComponent();
}

private void SendCustomerList()
{
startTime = DateTime.Now;
ServiceReference1.Service1Client client = new Service1Client();
List<TestObject> objects = CreateCustomerList(150);
byte[] compressed = PayloadHelper.ObjectToCompressedBytes(objects);
client.ProcessObjectsCompleted += new EventHandler<ProcessObjectsCompletedEventArgs>(client_ProcessObjectsCompleted);
client.ProcessObjectsAsync(compressed);
}

private void SendCustomerListOld()
{
startTime = DateTime.Now;
ServiceReference1.Service1Client client = new Service1Client();
List<TestObject> objects = CreateCustomerList(150);
client.ProcessObjectsOldWayCompleted += new EventHandler<ProcessObjectsOldWayCompletedEventArgs>(client_ProcessObjectsOldWayCompleted);
client.ProcessObjectsOldWayAsync( objects);
}

private List<TestObject > CreateCustomerList (int numberOfItems)
{
List<TestObject> objects = new List<TestObject>();
for (int i = 0; i < numberOfItems ; i++)
{
TestObject obj = new TestObject();
obj.FirstName = "Joe";
obj.LastName = "Blow" + i.ToString();
obj.Email = "joe" + i.ToString() + "@test.com";
obj.EntryDate = DateTime.Now;
obj.Id = i;
obj.GetsNewsLetter = true;
obj.Phone = "999-999-999" + i.ToString();
obj.Bio = "Very Cool Dude";
obj.BlogUrl = "http://yahoo.com";
objects.Add(obj);
}
return objects;
}

void client_ProcessObjectsOldWayCompleted(object sender, ProcessObjectsOldWayCompletedEventArgs e)
{
List<TestObject> objs = e.Result;
Dispatcher.BeginInvoke
(
() =>
{
EndTime = DateTime.Now;
this.Grid1.ItemsSource = objs;
var elapsed = EndTime - startTime;
this.Label1.Content = elapsed.TotalMilliseconds.ToString();
}
);
}


void client_ProcessObjectsCompleted(object sender, ProcessObjectsCompletedEventArgs e)
{
byte[] compressed = e.Result;
List<TestObject> objs = PayloadHelper.CompressedBytesToObject(compressed) as List<TestObject>;
Dispatcher.BeginInvoke
(
() =>
{
EndTime = DateTime.Now;
this.Grid1.ItemsSource = objs;
var elapsed = EndTime - startTime;
this.Label1.Content = elapsed.TotalMilliseconds.ToString();
}
);
}



private void ButtonNew_Click(object sender, RoutedEventArgs e)
{
SendCustomerList();
}

private void ButtonOld_Click(object sender, RoutedEventArgs e)
{
SendCustomerListOld();
}
}
}

NOTE: If you use this technique, it is important to put your domain model classes into a separate Silverlight Class Library project and compile them to an assembly that can then be referenced by both the Silverlight and the ASP.NET / WCF projects. If you do not do this, serialization may break.

When you run the app, you’ll see two big Buttons at the top, “New Way” and “Old Way”. Each will create a List<TestObject> with 100 instances of the TestObject class, send it over the wire, and at the server, we just append “:done” to each FirstName, and send it back to the Silverlight client where it is then displayed in a DataGrid, along with the estimated time in milliseconds in a label. Nothing fancy, just a “proof of concept”. I would caution that if you are using this on localhost, there really isn't any latency so the timing figures are kind of useless.

On my machine, running in Debug mode, it takes just 14ms to decompress and Deserialize the 100 objects in the List, and just 18ms to serialize and compress.




Incidentally, in order to get the payload sizes here I used Fiddler. But to do that you've got to get Fiddler to listen on Localhost. There's a Registry key: [HKEY_CURRENT_USER\Software\Microsoft\Fiddler2].

In that key, there is a REG_sz value "HookWithPac". Set that to "True" and you're good to go.

The downloadable solution is configured to use IIS. If you want to use the VS2010 development webserver, you'll need to change the Web configuration page and update your Service reference in the Silverlight application to match.



You can download the Visual Studio 2010 Silverlight 4 solution here.

By Peter Bromberg   Popularity  (8463 Views)