Silverlight Binary Serialization and Compression with WCF Services
By Peter Bromberg
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.
Popularity (5855 Views)
 |
| Biography - Peter Bromberg |
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites.
|  |
|
|
Article Discussion: Silverlight Binary Serialization and Compression with WCF Services
Desmond Davids replied
to Peter Bromberg at Friday, October 15, 2010 2:55 AM
Hey Peter,
I read your article and its really helpfull. But what we need to do is a bit different from your sample. We need to compress a DataSet or the XML on the server and decompress on our silverlight client. our problem at te moment is exactly what you explained..the data is too big...and still big even after compression, SharpZipLib compression ratio is good but too slow. With a simple object your sample will work since it is probably a silverlight object. But the DataSet coming from the Server is a System.Data DataSet and silverlight does not support this.
We have a Silverlight DataSet component from ComponentOne(C1.DataSet) that can read the System.Data DataSet byte array or xml. Is there a way that the Deserialze() method in your sample can return the DataSet byte[] instead of the System.Data DataSet itself. Or do you perhaps have some advice for me to compress my dotnet DataSet to the smallest possible size with fast compression/serialization and decompress/dezerialize at the silverlight client to xml/byte[] that my C1.DataSet can read?
Ive been at this for a couple of weeks now an still it takes about about 2.5 seconds just to get a 1000 rows, my data is almost 10 MB big compressed to 1,2 MB.
Peter Bromberg replied
to Desmond Davids at Friday, October 15, 2010 2:55 AM
First of all, you need to set the RemotingFormat on the DataSet to Binary in order to get away from the XML. This allows you to serialize to / from a more compact byte array. The updated Silverlight Serializer at Mike Talbots blog entry (which I've linked to above) should be able to handle DataSets.
You may then be able to use your Silverlight DataSet utility to handle these inside your Silverlight application.
Desmond Davids replied
to Peter Bromberg at Friday, October 15, 2010 2:55 AM
Hey i am sorry to post this here, i am trying to post a question regarding this article
http://www.eggheadcafe.com/articles/20031219.asp it a reasonbly old and i was trying to post aquestion in the forums but could see anything related to this also i wanted to direct my question to you. i tried both test applications in this article.
First of all i cant deserialise as my application kept falling over on deserialization of my DataTable. i also get this errror: "The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)"
then the biggest issue is performance. the compression takes 13 seconds in your tester application. this is for retrieving a 1000 rows at a size off about 8MB.
I am using Visual Studio 2010, sql server 2005, Windows 7 64 bit. .net framework 4
can you tell me if there is an updated version of The CompressDataSet library or what i could be doing wrong?
basically what i need is the fastest and most compact way off compressing a DataSet/DataTable. it can be compression of the DataSet object or xml as long as its fast and as small as possible so downloading is fast as well. i really thought that the above mentioned article would help but the performance is very slow and icant deserialize by DataTable.
Going through all the articles and blogs i could see you are really the ideal person to ask.
i would really appreciate any help and is really desperate right now.
Kind regards
Desmond
Peter Bromberg replied
to Desmond Davids at Friday, October 15, 2010 2:55 AM
Desmond,
What you need to do is post a question on the forums including enough sample code that somebody will be able to replicate the condition you describe. Either I or one of our many other astute readers will respond.
Francisco replied
to Peter Bromberg at Friday, October 15, 2010 2:55 AM
Hi I try implement in my code but i have error in:
private static object CreateObject(Type itemType)
{
try
{
return Activator.CreateInstance(itemType);
}
catch (Exception)
{
return itemType.GetConstructor(new Type[] { }).Invoke(new object[] { });
}
}
the message error is:
System.NullReferenceException was unhandled by user code
Message=Object reference not set to an instance of an object.
Source=Serialization
StackTrace:
at Serialization.SilverlightSerializer.CreateObject(Type itemType) in C:\Users\franciscor\Documents\Mis Proyectos\GeoViewer\lw_GeoViewer\Serialization\SilverlightSerializer.cs:line 1109
at Serialization.SilverlightSerializer.SerializeObjectAndProperties(Object item, Type itemType, BinaryWriter writer) in C:\Users\franciscor\Documents\Mis Proyectos\GeoViewer\lw_GeoViewer\Serialization\SilverlightSerializer.cs:line 1092
at Serialization.SilverlightSerializer.SerializeObject(Object item, BinaryWriter writer, Type propertyType) in C:\Users\franciscor\Documents\Mis Proyectos\GeoViewer\lw_GeoViewer\Serialization\SilverlightSerializer.cs:line 941
at Serialization.SilverlightSerializer.SerializeList(IList item, Type tp, BinaryWriter writer) in C:\Users\franciscor\Documents\Mis Proyectos\GeoViewer\lw_GeoViewer\Serialization\SilverlightSerializer.cs:line 957
at Serialization.SilverlightSerializer.SerializeObject(Object item, BinaryWriter writer, Type propertyType) in C:\Users\franciscor\Documents\Mis Proyectos\GeoViewer\lw_GeoViewer\Serialization\SilverlightSerializer.cs:line 935
at Serialization.SilverlightSerializer.Serialize(Object item, Stream outputStream) in C:\Users\franciscor\Documents\Mis Proyectos\GeoViewer\lw_GeoViewer\Serialization\SilverlightSerializer.cs:line 799
at Serialization.SilverlightSerializer.Serialize(Object item) in C:\Users\franciscor\Documents\Mis Proyectos\GeoViewer\lw_GeoViewer\Serialization\SilverlightSerializer.cs:line 834
at lw_GeoViewer.Web.Model.PayloadHelper.ObjectToCompressedBytes(Object obj) in C:\Users\franciscor\Documents\Mis Proyectos\GeoViewer\lw_GeoViewer\lw_GeoViewer.Web\Model\PayloadHelper.cs:line 14
at lw_GeoViewer.Web.Service1.OnloadEntityGeometryTest(Int64 sEntityId) in C:\Users\franciscor\Documents\Mis Proyectos\GeoViewer\lw_GeoViewer\lw_GeoViewer.Web\Service1.svc.cs:line 166
at SyncInvokeOnloadEntityGeometryTest(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
InnerException:
Peter Bromberg replied
to Francisco at Friday, October 15, 2010 2:55 AM
Please check Mike Talbot's blog entry for his latest version here:
http://whydoidoit.com/silverlight-serializer/
Glen replied
to Peter Bromberg at Friday, October 15, 2010 2:55 AM
I maybe missing something here, but wouldn't you just use the built in WCF BinarySerialization? I see value in this for storing data client side (getting/setting from ISO Storage). But for WCF this seems like overhead. When comparing times using WCF Binary, this method is actually slower (as expected due to the overhead).
WCF Binary Serialization is an excellent option. But, it doesn't compress the data. And I'm not sure if it will work coming from the client to the server from Silverlight. Whatever works for you is always the best choice, to be sure.