This SilverlightCache class can use either the DataContractSerializer to serialize
your objects for compression and storage, or it can use the much faster, more
compact Custom Binary Serializer that I featured in a previous article.
The DataContractSerializer does not require any pre-setup or interface implementation
on your classes. While the Custom Binary Serializer I present here requires you
to implement a simple interface on your classes, the extra work may well be worth
it. It is twice as fast, and serializes your objects to about one –half the size
that the DataContractSerializer does. In any case, there's code to do both, so
you decide.
In either case, I use a Silverlight port of MiniLzo by Markus Oberhumer that was
put together by Owen Emlen in C#. In general you can expect anywhere from 50% to 75% compression with this.
It is also reasonably fast.
Here is an example of how to implement the ICustomBinarySerializable interface in
a class:
[Serializable]
public class NotesList: ICustomBinarySerializable
{
public List<Note> Notes = new List<Note>();
#region ICustomBinarySerializable Members
public void WriteDataTo(BinaryWriter _Writer)
{
foreach(Note c in this.Notes )
{
_Writer.Write(c.ID);
_Writer.Write(c.EntryDate);
_Writer.Write(c.Subject);
_Writer.Write(c.NoteText);
_Writer.Write(c.Priority
);
_Writer.Write(c.Reminder);
//
You can also just do this:
//
c.WriteDataTo(_Writer);
}
}
public void SetDataFrom(BinaryReader _Reader)
{
while (_Reader.BaseStream.Position < _Reader.BaseStream.Length )
{
Note
c = new Note();
c.ID
= _Reader.ReadString();
c.EntryDate
= _Reader.ReadString();
c.Subject
= _Reader.ReadString();
c.NoteText
= _Reader.ReadString();
c.Priority
= _Reader.ReadInt32();
c.Reminder
= _Reader.ReadString();
//You
can also just do this:
//
c.SetDataFrom(_Reader );
this.Notes.Add(c);
}
}
}
#endregion
}
Note above that I use a while loop to handle reading multiple objects into a List<Note>
collection. In the example code, the Note class that is contained in the List
doesn’t even need to be marked Serializable as its CustomBinarySerializable
interface implementation is never called. However, I have it in the code for
the sake of completeness.
Here is an example of how you would use the Silverlight IsolatedStorage Compressed
Object Cache to Store, and then to reload a Collection:
private void butGenerate_Click(object sender, RoutedEventArgs e)
{
NotesList list = new NotesList();
Silverlight.Utils.CacheUtility<NotesList> util = null;
for(int i=0;i<5000;i++)
{
Note note = new Note(DateTime.Now.ToString(), "Subject Line " +i.ToString( ),"This is note " +i.ToString( ),1, "");
list.Notes.Add(note);
}
util = new CacheUtility<NotesList>();
util.IncreaseQuota();
util.SaveDataCustom(list);
// To do this with the DataContractSerializer (SLOWER!) comment line above, and uncomment
line below
// util.SaveData(list);
this.Text1.Text = util.UncompressedSize.ToString() + " Uncompressed, " + util.CompressedSize.ToString() +
" compressed. Time: " + util.ElapsedMilliseconds.ToString() + " ms.";
}
private void butShowData_Click(object sender, RoutedEventArgs e)
{
var util = new CacheUtility<NotesList>();
NotesList
list = util.LoadDataCustom();
this.Text1.Text = util.UncompressedSize.ToString() + " Uncompressed, " + util.CompressedSize.ToString() +
" compressed. Time: " + util.ElapsedMilliseconds.ToString() + " ms.";
// To do this with the DataContractSerializer (SLOWER!) comment line above, and uncomment
line below
// NotesList list = util.LoadData();
this.Grid1.ItemsSource = list.Notes;
}
}
Notice in the Generate button click handler, I make a call to increase the IsolatedStorage
quota. The user will get a Silverlight prompt the first time, and they can OK it.
The IncreaseQuota method is in the CacheUtility class.
Then I create 5,000 unique “Note” Instances and add each to the NotesList collection
class. FInally I call the SaveDataCustom method of the CacheUtility class. The
CacheUtility provides me with statistics on the sizes and elapsed time to perform
the operation.
Now let’s switch over the actual SaveDataCustom method in the CacheUtility class
and see how it works:
public void SaveDataCustom(T data)
{
DateTime
start = DateTime.Now;
using (IsolatedStorageFile isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication())
{
DeleteData();
using (IsolatedStorageFileStream fileStream = isolatedStorageFile.CreateFile(CacheFilename))
{
CustomBinarySerializer.CustomBinaryFormatter
f = new CustomBinaryFormatter();
f.Register<T
>(1);
MemoryStream
ms = new MemoryStream();
f.Serialize(ms,
data);
ms.Seek(0,
0);
byte[] b = ms.ToArray();
ms.Dispose();
UncompressedSize
= b.Length;
byte[] compressed = b.Compress();
CompressedSize
= compressed.Length;
fileStream.Write(compressed,
0, compressed.Length);
}
}
DateTime
end = DateTime.Now;
TimeSpan
elapsed = end - start;
this.ElapsedMilliseconds = elapsed.TotalMilliseconds;
}
Above, you can see that we first get our IsolatedStorage, then we delete any previous
data, then we create our file. Then we create our CustomBinaryFormatter and register
the type with it. We create a MemoryStream, serialize the object graph into it,
rewind the stream.
Then we get the byte array out of the stream and compress it. Finally, write it to
the file. This entire operation may take 250 milliseconds or so with 5,000 of
these objects stored. The original 492,788 bytes of serialized object graph is
compressed down to only 227,928 bytes – a savings of more than 53 percent. So
even without requesting to increase the quota, we’ve got plenty of storage space.
You can specify different file names in order to store multiple objects or collections
of objects.
You do not "have to" use this concept only for IsolatedStorage. You can
serialize and compress objects to send over the wire, for example. Your WCF Service
at the other end would have the same code to decompress and deserialize your
"stuff" and do what you need to do - update a database, and so on.
The whole concept of this is to create a utility that can easily be “plugged in”
to any number of applications to solve a common problem. You can download the complete Visual Studio 2008 SIlverlight 3 project.