The HttpCookie is a much maligned little fellow. Everybody loves him, and everybody hates him. Regardless of your position on this important matter, there can be no dispute that cookies are valuable.

The maximum size that a cookie can be depends on which browser is being used. Since you normally have no control over which browser your visitors are using, you need to design your cookie usage to cater for the browser that sets the smallest size limit for cookies. Testing of cookies of various sizes with all of the different browsers reveals that they will all handle cookies of up to 4000 bytes, but some browsers malfunction once cookies exceed that size.
So, how about if we just compress our cookies? "Aha!", you say, "We could try that!" Thus, the Compressed Cookie is born. Now when FatBoy raids the Cookie Factory, we are going to put him on a diet! There are a few side benefits of compressing cookies:
1) You can pass any serializable object into the BinaryFormatter, and get a compact byte stream that can be then turned into a Base64 encoded string and saved as a cookie. So, we can store classes in cookies!
2) The compression and subsequent Base64 encoding more or less encrypts the cookie. It's not really encrypted, but it would be pretty hard for the casual hacker to figure out how all that glop got that way!
3) With a good compression ratio, we should certainly be able to surpass the 4000 character "safe" limit on cookie length.
Without further adieu, I present my CookieCompression Class:
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;
namespace CompressedCookies
{
public static class CpCookies
{
public static bool Set(string cookieName, object cookieValue,
DateTime expirationDate)
{
bool retval = true;
try
{
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, cookieValue);
byte[] inbyt = ms.ToArray();
System.IO.MemoryStream objStream = new MemoryStream();
System.IO.Compression.DeflateStream objZS = new System.IO.Compression.DeflateStream(objStream,
System.IO.Compression.CompressionMode.Compress);
objZS.Write(inbyt, 0, inbyt.Length);
objZS.Flush();
objZS.Close();
byte[] b = objStream.ToArray();
string sCookieVal = Convert.ToBase64String(b);
HttpCookie cook = new HttpCookie(cookieName);
cook.Value = sCookieVal;
cook.Expires = expirationDate;
HttpContext.Current.Response.Cookies.Add(cook);
}
catch
{
retval = false;
throw ;
}
return retval;
}
public static object Get(string cookieName)
{
object retval = null;
try
{
byte[] bytCook = Convert.FromBase64String(HttpContext.Current.Request.Cookies[cookieName].Value);
MemoryStream inMs = new MemoryStream(bytCook);
inMs.Seek(0, 0);
DeflateStream zipStream = new DeflateStream(inMs,
CompressionMode.Decompress, true);
byte[] outByt = ReadFullStream(zipStream);
zipStream.Flush();
zipStream.Close();
MemoryStream outMs = new MemoryStream(outByt);
outMs.Seek(0, 0);
BinaryFormatter bf = new BinaryFormatter();
retval = (object)bf.Deserialize(outMs, null);
}
catch(Exception ex)
{
throw ex;
}
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;
}
private static byte[] ReadFullStream(Stream stream)
{
byte[] buffer = new byte[32768];
using (MemoryStream ms = new MemoryStream())
{
while (true)
{
int read = stream.Read(buffer, 0, buffer.Length);
if (read <= 0)
return ms.ToArray();
ms.Write(buffer, 0, read);
}
}
}
}
}
The methods in the above static class should be self-explanatory. The BinaryFormatter doesn't care what you ask it to serialize into a byte stream, as long as the object is marked [Serializable]. So, we serialize the object, then we compress it using the built-in System.IO.Compression DeflateStream, then convert the resultant byte array into a Base64 encoded string, and save the cookie.
To perform the reverse operation, we retrieve our compressed cookie, convert it back into a byte array from the saved Base64 encoded string, decompress it, and run it back through the BinaryFormatter to get back our object, whatever it may have been. Of course, it's up to the caller to cast the object to it's actual type, and we are done!
Actual tests show that the level of compression you can get with this technique will vary depending on what the serialized stream looks like, but it still has interesting possibilities, most notably the ability to pass a class into the method and have it saved "as is" to a cookie, and of course the fact that the cookie text is obfuscated from prying eyes quite nicely.
The sample Web Application Project in the downloadable solution creates a Test class with user information, along with an ArrayList of 103 "Favorite Movies":
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections;
namespace CompressedCookiesWeb
{
[Serializable]
public class Test
{
public Test(string firstName, string lastName, string address,
string city, string state, string zipCode, string email,
string userName, string passWord)
{
this._firstName = firstName;
this._lastName = lastName;
this._address = address;
this._city = city;
this._state = state;
this._zipCode = zipCode;
this._email = email;
this._userName = userName;
this._password = passWord;
}
public ArrayList FavoriteMovies = new ArrayList();
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
private string _address;
public string Address
{
get { return _address; }
set { _address = value; }
}
private string _city;
public string City
{
get { return _city; }
set { _city = value; }
}
private string _state;
public string State
{
get { return _state; }
set { _state = value; }
}
private string _zipCode;
public string ZipCode
{
get { return _zipCode; }
set { _zipCode = value; }
}
private string _email;
public string Email
{
get { return _email; }
set { _email = value; }
}
private string _userName;
public string UserName
{
get { return _userName; }
set { _userName = value; }
}
private string _password;
public string Password
{
get { return _password; }
set { _password = value; }
}
}
}
This is saved as a compressed cookie, then we redirect to a second page where it is retrieved, decompressed and deserialized back into our test class, and the contents written into the page. The resulting page in my test app looks like so:
Peter Bromberg
101 Park Avenue West
New York, NY 10021
Email: pbromberg@nospammin.yahoo.com
U: petey P: whodunnit
=============================
Favorite Movies:
=============================
Movie: My Life as a Dog
Movie: Tampopo
Movie: Babette's Feast
Movie: The Thief
Movie: Monty Python
Movie: Colonel Redl
Movie: 2001: A Space Odyssey (1968)
Movie: 42nd Street (1933)
Movie: The 400 Blows (1959)
Movie: All About Eve (1950)
Movie: Annie Hall (1977)
Movie: Ashes and Diamonds (1958)
Movie: L'Atalante (1934)
Movie: The Bank Dick (1940)
Movie: The Battleship Potemkin (1925)
Movie: The Birth Of A Nation (1915)
Movie: Blow-Up (1966)
Movie: Bonnie And Clyde (1967)
Movie: Breathless (1960)
Movie: Bringing Up Baby (1938)
Movie: Casablanca (1942)
Movie: The Chant of Jimmie Blacksmith (1978)
Movie: Children of Paradise (1945)
Movie: Chinatown (1974)
Movie: Citizen Kane (1941)
Movie: Close Encounters of the Third Kind (1977)
Movie: Closely Watched Trains (1967)
Movie: Close-up (1990)
Movie: Dance, Girl, Dance (1940)
Movie: The Decalogue (1988)
Movie: Diary of a Country Priest (1951)
Movie: Diner (1982)
Movie: Do the Right Thing (1989)
Movie: La Dolce Vita (1959)
Movie: Double Indemnity (1944)
Movie: Duck Soup (1933)
Movie: Easy Rider (1969)
Movie: Enter the Dragon (1973)
Movie: The Entertainer (1960)
Movie: The Exorcist (1973)
Movie: Faces (1968)
Movie: Fargo (1996)
Movie: Frankenstein (1931) and The Bride of Frankenstein (1935)
Movie: The General (1927)
Movie: The Godfather (1972) and The Godfather Part II (1974)
Movie: Gone With The Wind (1939)
Movie: The Gospel According to St. Matthew (1964)
Movie: The Graduate (1967)
Movie: Greed (1924)
Movie: Happy Together (1997)
Movie: High Noon (1952)
Movie: The Invasion of the Body Snatchers (1956)
Movie: Jailhouse Rock (1957)
Movie: Ju Dou (1990), Raise the Red Lantern (1991), Red Sorghum (1987)
Movie: Killer of Sheep (1977)
Movie: L.A. Confidential (1997)
Movie: Landscape in the Mist (1988)
Movie: Lawrence of Arabia (1962)
Movie: M (1931)
Movie: The Maltese Falcon (1941)
Movie: The Man With a Movie Camera (1929)
Movie: The Marriage of Maria Braun (1978)
Movie: Metropolis (1927)
Movie: Modern Times (1936)
Movie: Mr. Smith Goes to Washington (1939)
Movie: Nashville (1975)
Movie: The Night of the Hunter (1955)
Movie: Night of the Living Dead (1968)
Movie: Nosferatu (1922)
Movie: Los Olvidados (1950)
Movie: On The Waterfront (1954)
Movie: Open City (1945)
Movie: The Palm Beach Story (1942)
Movie: Pandora's Box (1928)
Movie: The Passion of Joan of Arc (1928)
Movie: Pather Panchali (1956), Aparajito (1958), The World of Apu (1960)
Movie: The Piano (1993)
Movie: Psycho (1960)
Movie: The Public Enemy (1931)
Movie: Pulp Fiction (1994)
Movie: Raging Bull (1980)
Movie: Rashomon (1950)
Movie: Rebel Without a Cause (1955)
Movie: The Rules of the Game (1939)
Movie: Schindler's List (1993)
Movie: The Searchers (1956)
Movie: The Seven Samurai (1954)
Movie: The Seventh Seal (1957)
Movie: Singin' In The Rain (1952)
Movie: Star Wars (1977)
Movie: La Strada (1955) and Nights of Cabiria (1957)
Movie: Sunrise (1927)
Movie: Sunset Boulevard (1950)
Movie: The Thief of Bagdad (1924)
Movie: Tokyo Story (1953)
Movie: Top Hat (1935)
Movie: Touch Of Evil (1958)
Movie: Trouble in Paradise (1932)
Movie: Ugetsu Monogatori (1953)
Movie: Unforgiven (1992)
Movie: Les Vampires (1915)
Movie: Vertigo (1958)
Movie: The Wild Bunch (1969)
Movie: Winchester '73 (1953)
Movie: The Wizard of Oz (1939)
Movie: Written on the Wind (1956)
NOTE: If you are going to save DataSets as Compressed Cookies, be sure to set the SerializationFormat to "Binary":
DataSet ds = GetData();
ds.RemotingFormat = SerializationFormat.Binary;
In my tests, I have been able to store nearly 9,000 lines of text in an ArrayList, in a compressed cookie.
No cookies were harmed in the creation of this article. Contrary to what you may have read on the Internet, Compressed Cookies taste fine.
N.B. This article has been updated to show the use of 7Zip (LZMA) for even higher compression ratios. NOTE: An alert reader noticed that the Delete method used Request.Cookies... This should be Response, since we want to overwrite an existing cookie with an expiry date in the past to delete it. The downloadable source code has been updated with the corrections.
For info on LZMA (7Zip) compression:
Download the Visual Studio 2005 Solution that accompanies this article.
Hi Peter, interesting post. I was wondering what's the variance of the compression rate you achieved in your tests.
the content that is being compressed. In general, Gzip / deflate ("zip") compression achieves better rates when there is a lot of repeating data in the source. You could see compression as high as 85%, or as low as only 20%, depending on the size and content.
Dear Peter, your article helped me a lot. I think storing state information in cookies in this way is great. I downloaded the source code and tailored to my needs. There's one line of code I am sure is in error though. This is in the Delete method of CpCookies class. You used "Request" instead of "Response" property, which cannot overwrite the existing cookie with a negative expiration value to be expired, deleted.
public static bool Delete(string cookieName)
{
bool retval = true;
try
{
// This is the faulty line and Request property must be replaced by Response
HttpContext.Current.Request.Cookies[cookieName].Expires = DateTime.Now.AddDays(-365);
}
catch
{
retval = false;
}
return retval;
}
Am I wrong?
to "Response". Must have been in a hurry. Thanks for the tip! The article, and the download have been corrected.
i have downloaded your code & it's really helped me a lot, i just want to refer to the speed difference between the default compression & the 7-Zip (LZMA) compression you thankfully introduced it ,
LZMA is indeed smaller than the default with about 30% ratio
but default is Faster than it about 43 times !!
so if our object after serialization & compression with default method is less than 4k (in most cases), then we should not use LZMA ,since we will need to use the cookie almost all over the website.
it would be huge performance gab between them.
thanks,
Eslam Badawy