Viewstate Serialization, one more time!

Note: this entry has moved.

I just read Scott Mitchell’s article about viewstate where he’s explaining how viewstate serialization works and he’s saying:

 

“…The LosFormatter can serialize any type of object that can be serialized by the BinaryFormatter class…”

 

Watch out! That’s partially wrong. That is not complete and might lead you to believe that you need to make your type serializable by the BinaryFormatter in order to be able to store it into viewstate: that’s not true. As I explained in a previous post the LosFormatter will try using an associated type converter *before* getting to a BinaryFormatter. So, a type that is not serializable by a BinaryFormatter can still be serialized by the LosFormatter. It’s a real pity that none of the info that I’ve been detailing on some of my latest posts got mentioned into that article :-(

 

The reason I’ve been sharing this viewstate low-level details is because I couldn’t find them anywhere else on the web… and there is lot of misconceptions about how this really works, you can easily notice that by browsing the public newsgroups… Hey, even the Mono guys got it wrong at first but after I shout they quickly fix it.

 

6 Comments

  • I found the best way to get to the bottom of how it works is to use the File Disassembler add-in for reflector then just follow through the execution path. To be honest as is ViewState feels unfinished - stuff like only supporting 3DES for encryption, the formatter stuff you mentioned etc...I haven't really dug into ViewState in ASP.NET 2.0 too much yet but it doesn't look very much different - hopefully I'm wrong and there's a ViewState provider model (anyone know?)



  • Yes, Whidbey brings good news regarding this: a nice PageStatePersister base class with two specializations, one for saving viewstate to a hidden field and the other one for saving viewstate to session. You can easily create your own, i.e. a OldVoyagerPageStatePersister that breaks viewstate in chunks of 10k to avoid a bug in the browser that limit hidden field sizes to 10kb... ugly!! ;-)

  • Not to be pedantic, but while the statement might not be complete, it is not wrong, not even partially.



    "...The LosFormatter can serialize any type of object that can be serialized by the BinaryFormatter class..."



    This is a true statement, no? That is, you can deconstruct this into predicate calculus by saying p implies q, where p is "The BinaryFormatter can serialize the class" and q is "The LosFormatter can serialize the class." So for this to be incorrect, there would need to be a case where p implies not q, or namely, an instance where a class can be serialized by BinaryFormatter but NOT by LosFormatter. Ok, I'm ending pedantic mode...



    In any event, I didn't discover your blog until after the article had been written, I'm afraid. You do have some great low-level info on ASP.NET view state, obviously, and had I been aware of this blog at the time of writing I would have most definitely linked to it. :-)





  • That was a bad wording, sorry. I should have used "incomplete" or such. The reason I wrote the post is because that incomplete (hope you like this now... :-P) sentence might lead people to believe that they have to make their types serializable by the BinaryFormatter in order to store them into viewstate, which is not necessary. Also I was surprised to not see a single mention to type converters into the article :-(



    Thanks for correcting my english!

  • An in-depth article even mentioning the LosFormatter can't skip talking about how TypeConverters fit in the picture (as Victor did), as they are much more important to efficient ViewState serialization than BinaryFormatter (impossible to customize) format.

    Sorry, but that qualifies for a non-in-depth article for me.

  • this is working for me...

    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 description for PagePersisterBasePage
    ///
    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 ViewStateBag;

    protected override void SavePageStateToPersistenceMedium(object viewState)
    {
    this.ViewStateBag = ((List)Session["ViewStateBag"]);
    if (this.ViewStateBag == null)
    {
    this.ViewStateBag = new List();
    }

    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();
    }
    }

Comments have been disabled for this content.