Don't let the BinaryFormatter get at it!

Note: this entry has moved.

All the stuff written out there about viewstate will alert you that it was designed to handle only the following types: int, String, Boolean, Pair, Triple, Array, ArrayList, Hashtable and that trying to store anything else will incur in a performance loss. This is a well-known fact but what is not so well-known is the real why behind this.

 

One of the things I like to mention in my talks while addressing viewstate performance is something like “don’t let the BinaryFormatter get at your data”. I like all the “what??” this phrase causes at first :-) but after a bit of explanation everything comes back to normal again.

 

So, let’s see how the LosFormatter type –which happens to be the viewstate serializer- was designed for dealing with type serialization.

 

Its first approach is a very common one; it consists of checking if the type its dealing with it’s a simple one that it knows how to quickly serialize (the types listed at the very beginning of this post). In case it’s dealing with such a type it just needs to write out some form of header (usually a type code, which will be used to know what type follows) and then the customized (and fast) serialization happens. Note this is not really a novel ideal and it is exactly the same AltSerialization type already does.

 

Now, if the type LosFormatter is trying to serialize it’s not a supported one it will check if that type has an associated TypeConverter (yes, this implies some reflection code so you can start feeling the perf loss already, can you?). If a type converter is found then it will just say: “Hey you, type converter! Could you please serialize this type to a string representation for me please?”. It will then just use the output produced by the type converter.

 

But what happens if the type hasn’t an associated type converter. Well in this case the LosFormatter will say “Oh damn!, I don’t know how to serialize this type by myself and worse yet, it doesn’t have an associated type converter available to do the work for me; what am I gonna do now?” Its own answer is: “Let’s use the BinaryFormatter!”. Note that we’re talking here about instantiating a new BinaryFormatter and feeding it with the type in question. As you can imagine this is *lot* more work than any of the previous two steps, so you *really* want to avoid getting here in the first place.

 

 

Let’s put the above explanation into perspective; let’s say you’ve the following type:

 

[Serializable]

public class Customer {

     private String _lastname;

     private String _firstname;

     private String _address;

     private int _age;

     private int _code;

 

public Customer (String lastName, String firstName, String address, int age, int code) {

this._lastname = lastName;

           this._firstname = firstName;

           this._address = address;

           this._age = age;

           this._code = code;

 

     public String LastName {

           get {return _lastname;}

           set {_lastname = value;}

     }

     public String FirstName

     {

           get {return _firstname;}

           set {_firstname = value;}

     }

     public String Address

     {

           get {return _address;}

           set {_address = value;}

     }

     public int Age

     {

           get {return _age;}

           set {_age = value;}

     }

     public int Code

     {

           get {return _code;}

           set {_code = value;}

     }

}

 

Now let’s say you need to persist an instance of this Customer type to viewstate. If you just do so, the type won’t be one of the supported ones by LosFormatter and it won’t have a type converter associated so the BinaryFormatter will kick. Let’s try it:

 

 

Customer cust = new Customer("Garcia Aprea","Victor","1802 4th Clarius Ave.",29,31987);

LosFormatter los = new LosFormatter();

StringWriter sw = new StringWriter();

los.Serialize (sw, cust);

String resultSt = sw.GetStringBuilder ().ToString();

int size = sw.GetStringBuilder ().ToString().Length;

 

 

If you read size value you will notice that you just paid 436 bytes to serialize a Customer. For three short strings and two integers that seems just too much.

Let’s now code a type converter that knows how to persist a Customer to a String and get back a Customer from a String:

 

 

     public class CustomerConverter : System.ComponentModel.TypeConverter

     {

 

           public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)

           {

                if (sourceType == typeof(String))

                     return true;

                return base.CanConvertFrom (context, sourceType);

           }

 

           public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)

           {

                if (destinationType == typeof(String))

                     return true;

                return base.CanConvertTo (context, destinationType);

           }

 

           public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)

           {

                String state = value as String;

                if (state == null)

                     return base.ConvertFrom (context, culture, value);

                String []parts = state.Split ('#');

                return new Customer (parts[0], parts[1], parts[2], System.Convert.ToInt32 (parts[3]), System.Convert.ToInt32 (parts[4]));

           }

 

           public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)

           {

                if (destinationType == null)

                     throw new ArgumentException ("destinationType");

                Customer cust = value as Customer;

                if (cust != null)

                     return cust.LastName + "#" + cust.FirstName + "#" + cust.Address + "#" + cust.Age.ToString() + "#" + cust.Code.ToString();

                else

                     return base.ConvertTo (context, culture, value, destinationType);

           }

     }

 

And let’s attach it to the Customer type:

 

[TypeConverter (typeof(CustomerConverter))]

public class Customer

 

Run again the previous code used to feed a Customer instance to the LosFormatter and this time the footprint will be of 196 bytes! That’s quite a reduction, isn’t it?

 

Lastly, let’s play by LosFormatter rules and offer it a type that it knows how to handle:

 

Triplet t = new Triplet (cust.LastName, cust.FirstName, cust.Address);

Pair p = new Pair (cust.Age, cust.Code);

Pair custPair = new Pair (t,p);

 

Now feed the LosFormatter with custPair and not just an instance of Customer; this time the footprint will be of… 60 bytes! Another great improvement over the original 436 bytes.

 

I’m hoping that you can now see there are *real* benefits of knowing this stuff and that you could start taking advantage of it right away! :-)

Published Tuesday, May 11, 2004 6:24 PM by vga
Filed under:

Comments

# RE: Don't let the BinaryFormatter get at it!

Thursday, May 13, 2004 11:49 AM by Daniel Cazzulino
Very interesting! So I guess it would be possible to write a generic ViewStateFriendlyTypeConverter that automatically gets those triplet and pairs... ;)
I'm thinking that such approach, coupled with dynamic IL emiting (to avoid reflection after first call) would be a killer solution!

# re: Don't let the BinaryFormatter get at it!

Sunday, May 16, 2004 2:41 PM by Victor Garcia Aprea
Gabriel,
The assembly qualified name of the custom type Customer needs to be stored once in viewstate; if you just copy & pasted the code found here, chances are that you´re using a shorter assembly name and maybe shorter full type name also.

# re: Don't let the BinaryFormatter get at it!

Thursday, August 5, 2004 5:52 PM by Amit
Thanks! I had already written the typeconverter but had got the ConvertTo method wrong. Works fine now.

# re: Don't let the BinaryFormatter get at it!

Tuesday, December 12, 2006 3:55 AM by robbie

Excellent post !!!!!

# re: Don't let the BinaryFormatter get at it!

Friday, September 17, 2010 11:38 AM by Belial

it might be little late but im testing this...

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.IO;

using System.IO.Compression;

using System.Collections;

using System.Collections.Generic;

using System.Runtime.Serialization.Formatters.Binary;

/// <summary>

/// Summary description for PagePersisterBasePage

/// </summary>

public class PagePersisterBasePage : System.Web.UI.Page //Page

{

   // creates a new instance of a GUID for the current request

   private Guid pViewStateFilePath = Guid.NewGuid();

   private List<string> ViewStateBag;

   protected override void SavePageStateToPersistenceMedium(object viewState)

   {

       this.ViewStateBag = ((List<string>)Session["ViewStateBag"]);

       if (this.ViewStateBag == null)

       {

           this.ViewStateBag = new List<string>();

       }

       MemoryStream writer = new MemoryStream();

       byte[] viewStateBytes = ObjectToByteArray(viewState);

       writer.Write(viewStateBytes, 0, viewStateBytes.Length);

       writer.Close();

       byte[] bytes = Compress(writer.ToArray());

       Session[pViewStateFilePath.ToString()] = Convert.ToBase64String(bytes);

       base.SavePageStateToPersistenceMedium(new Pair(null, pViewStateFilePath));

       this.ViewStateBag.Add(pViewStateFilePath.ToString());

       Session.Add("ViewStateBag",(object)this.ViewStateBag);

   }

   private byte[] ObjectToByteArray(Object obj)

   {

       if (obj == null)

           return null;

       BinaryFormatter bf = new BinaryFormatter();

       MemoryStream ms = new MemoryStream();

       bf.Serialize(ms, obj);

       return ms.ToArray();

   }

   private Object ByteArrayToObject(byte[] arrBytes)

   {

       MemoryStream memStream = new MemoryStream();

       BinaryFormatter binForm = new BinaryFormatter();

       memStream.Write(arrBytes, 0, arrBytes.Length);

       memStream.Seek(0, SeekOrigin.Begin);

       Object obj = (Object)binForm.Deserialize(memStream);

       return obj;

   }

   protected override object LoadPageStateFromPersistenceMedium()

   {

       Pair vs = base.LoadPageStateFromPersistenceMedium() as Pair;

       if (vs != null)

       {

           if (Session[vs.Second.ToString()] == null)

           {

               return null;

           }

           else

           {

               string viewStateString = String.Empty;

               viewStateString = (Session[vs.Second.ToString()]).ToString();

               byte[] bytes = Convert.FromBase64String(viewStateString);

               bytes = Decompress(bytes);

               object viewStateObject = ByteArrayToObject(bytes);

               return viewStateObject;

           }

       }

       else

           return null;

   }

   public static byte[] Compress(byte[] data)

   {

       MemoryStream output = new MemoryStream();

       GZipStream gzip = new GZipStream(output,CompressionMode.Compress, true);

       gzip.Write(data, 0, data.Length);

       gzip.Close();

       return output.ToArray();

   }

   public static byte[] Decompress(byte[] data)

   {

       MemoryStream input = new MemoryStream();

       input.Write(data, 0, data.Length);

       input.Position = 0;

       GZipStream gzip = new GZipStream(input,CompressionMode.Decompress, true);

       MemoryStream output = new MemoryStream();

       byte[] buff = new byte[64];

       int read = -1;

       read = gzip.Read(buff, 0, buff.Length);

       while (read > 0)

       {

           output.Write(buff, 0, read);

           read = gzip.Read(buff, 0, buff.Length);

       }

       gzip.Close();

       return output.ToArray();

   }

}