ASP.NET - Silverlight: My Life as a Compressed Cookie Redux

The "compressed cookie" is a way to securely store larger amounts of information in a regular HTTP cookie. It works in both normal ASPX pages and in Silverlight.

The idea for the original title of this article came from the 1985 Swedish foreign film "My Life As a Dog" which I highly reccommend. Few Hollywood movies ever achieve the sensitivity, simple beauty and depth of some of the foreign films I've come to love. The "Redux" part is because it is, as the Latin phrase indicates, "back again" - improved, and now - Silverlight compatible.

I've always been an "experimenter". When I was a kid, I reengineered my CB radio with an illegal 40 watt power tube (the legal limit then was only 5 watts). I also set Hi Tor mountain in Haverstraw, NY on fire. And that was just the beginning. Now, I experiment a bit more safely with code, and to a lesser extent with digitally processed photography (Maya) and music (hang drum, didjeridoo, and flute). Some experiments don't work, and need to be thrown out. But others can produce really useful things when you are a programmer building up your personal codebase of "tricks". The compressed cookie, which I like to think I invented, is one of those successful experiments.


The question now becomes: Why would somebody want to compress an HTTP Cookie?  There are several reasons I can think of, and all of them seem valid to me:

1) Because you can!
2) Store more information in a single cookie. A LOT more information -- for example,  an entire serialized class instance with all it's property values.
3) Keep prying eyes out of your cookie business. Compressed cookies are virtually impossible to figure out and decode.
4) Performance on both storing and retrieving compressed cookies is excellent.
5) This all works in Silverlight too.

To create a compressed cookie, we need to be able to do four things:

1. Efficiently serialize whatever object instance we want to store to a byte array.
2. Compress the byte array to allow for more storage within the legal limit for an HTTP cookie (approximately 4096 characters).
3. Base64 encode the compressed bytes so that they can be stored as legal HTTP Cookie characters.
4. Set a normal HTTP browser cookie with the correct name, value and expiration date using our compressed payload as the value.

To read a compressed cookie, we simply perform the above operations in reverse:

1. Read the cookie the way we normally would do.
2. Get the compressed byte array from the base64 encoded cookie value.
3. Decompress the byte array.
4. Deserialize the byte array into an instance of the original type so it can be used in our code again.

Compressed cookies can be used to store settings, to persist state between page navigations, and other uses. The algorithm itself does not have to be used only for cookies - the same code can be modified so that it will serialize and compress objects for transmission over the wire instead.

Let's have a look at some code!

For the compression algorithm, I chose the QuickLZ data compression library by Lasse Mikkel Reinhold. This choice was based on my research in this article on comparison of Data Compression Algorithms.

The QuickLZ algorithm is Silverlight-compatible and provides excellent compression ratios combined with very fast decompression, making it the ideal choice for this project. The only things I have added to this class are two extension methods on the byte[] type, which makes it easier to use.

For Serialization and Deserialization, I chose Mike Talbot's Silverlight Serializer class. You can see Mike's work here.   I made a couple of contributions to Mike's work, most notably a way to deserialize a type that has no parameterless constructor, and he has already incorporated my ideas.  Silverlight Serializer is not only very fast, it also produces byte arrays that are about 30% smaller than the BinaryFormatter - which of course, is not even available in Silverlight.

You can examine either of these classes in the downloadable solutions I've made available at the bottom of this article.

Here is my  standard CpCookies class that handles everything:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.IO.Compression;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using QuickLZSharp;
using Serialization;

namespace CompressedCookies
{
    public static class CpCookies
    {
         private static object DeserializeBase64Decompress(string cookieName)
         {
             return SilverlightSerializer.Deserialize(Convert.FromBase64String(HttpContext.Current.Request.Cookies[cookieName].Value).Decompress());
         }

         private static void SetCookie(string cookieName, string sCookieValue, DateTime expirationDate)
        {
            HttpCookie cook = new HttpCookie(cookieName);
            cook.Value = sCookieValue;
            cook.Expires = expirationDate;
             HttpContext.Current.Response.Cookies.Add(cook);
        }

         public static int Set(string cookieName, object cookieValue, DateTime expirationDate)
        {
            int siz =0;
             try
            {
                string sCookieVal = SerializeAndCompressBase64(cookieValue);
                siz = sCookieVal.Length;
                 if (siz > 4095)
                    throw new InvalidOperationException("Cookie value cannot exceed 4095 characters.");
                SetCookie(cookieName ,sCookieVal ,expirationDate );
            }
            catch
            {
                throw ;
            }
            return siz;
        }

         private static string SerializeAndCompressBase64( object cookieValue)
         {
             return Convert.ToBase64String(SilverlightSerializer.Serialize(cookieValue).Compress());
        }

         
         
        public static object Get(string cookieName)
        {
            object retval = null;
            try
            {
              retval = DeserializeBase64Decompress(cookieName);
            }
            catch
            {                
                throw ;
            }
            return retval;
        }

      

        public static bool Delete(string cookieName)
        {
            bool retval = true;
            try
            {
                 HttpContext.Current.Response.Cookies[cookieName].Expires = DateTime.Now.AddDays(-365);
            }
            catch
            {
                retval = false;
            }
             return retval;
        }

      
    }
}

The class should be pretty much self-explanatory. To use this, we perform our set operation as follows:

CpCookies.Set("Test2", t, DateTime.Now.AddDays(100)); // Where t is your type to be stored.

And to retrieve and use an existing cookie:

Test   t2 = (Test)CpCookies.Get("Test2"); // "Test" is the sample class in the solution.

The Set method returns the size of your cookie content and throws an exception if it is over 4095 bytes. However, it is better to err on the side of caution since the maximum allowed cookie size in most browsers includes the cookie name and expiration - everything in an HTTP cookie is stored as one big delimited string.

Here is the Silverlight version - it's a bit different since in Silverlight we are using the HtmlPage.Document object:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Browser;
using System.IO;
using QuickLZSharp;
using Serialization;

namespace CompressedCookies
{
    public static class CpCookies
    {
         private static object DeserializeBase64Decompress(string cookieName)
        {
            string[] cookies = HtmlPage.Document.Cookies.Split(';');
            object o = null;
            foreach (string cookie in cookies)
            {
                string[] keyValue = cookie.Split('=');
                 if (keyValue.Length == 2)
                 {
                      if (keyValue[0].ToString() == cookieName)
                    o= SilverlightSerializer.Deserialize(Convert.FromBase64String(keyValue[1]).Decompress());
                      break;
                 }
             }
             return o;
        }

         private static void SetCookie(string cookieName, string sCookieValue, DateTime expirationDate)
        {
            StringBuilder fullCookie = new StringBuilder();
            fullCookie.Append(string.Concat(cookieName, "=", sCookieValue ));
             fullCookie.Append(string.Concat(";expires=", expirationDate.ToString("R")));
            HtmlPage.Document.SetProperty("cookie", fullCookie.ToString());
        }

        public static int Set(string cookieName, object cookieValue, DateTime expirationDate)
        {
            int siz =0;
             try
            {
                string sCookieVal = SerializeAndCompressBase64(cookieValue);
                siz = sCookieVal.Length;
                 if (siz > 4095)
                    throw new InvalidOperationException("Cookie value cannot exceed 4095 characters.");
                SetCookie(cookieName ,sCookieVal ,expirationDate );
            }
            catch
            {
                throw ;
            }
            return siz;
        }

         private static string SerializeAndCompressBase64( object cookieValue)
         {
             return Convert.ToBase64String(SilverlightSerializer.Serialize(cookieValue).Compress());
        }

         
         
        public static object Get(string cookieName)
        {
            object retval = null;
            try
            {
              retval = DeserializeBase64Decompress(cookieName);
            }
            catch
            {                
                throw ;
            }
            return retval;
        }
         
         public static bool Delete(string cookieName)
        {
            bool retval = true;
            try
            {
               SetCookie(cookieName,null,DateTime.Now.AddDays(-365));
            }
            catch
            {
                retval = false;
            }
             return retval;
        }
    }
}


You can download the ASP.NET compatible version here, and the Silverlight version here. When running the Silverlight version, just reload the page in the browser, and  the App.Xaml.cs class will change the RootVisual after running the app one time and detecting that the "Test2" cookie has been set, so that it will instead display the Xaml page that reads and shows the contents of  the cookie that you set:

   private void Application_Startup(object sender, StartupEventArgs e)
         {
             if(HtmlPage.Document.Cookies.Contains("Test2") )
                 this.RootVisual = new Page2();
             else
                 this.RootVisual = new MainPage();
        }

By Peter Bromberg   Popularity  (2900 Views)