The Inquisitive Coder – Davy Brion's Blog

Trying to walk that thin line between intelligence and ignorance

Reducing ViewState Size

Posted by Davy Brion on September 1st, 2009

I dislike ViewState as much as the next guy, but when you’re working with ASP.NET WebForms, you just can’t avoid it. In some cases, the size of the ViewState can become so big that it significantly increases the load time of pages due to the extra bandwidth consumption. The correct solution would obviously be to reduce the size of the ViewState in those pages as much as you can, but it’s not always feasible to do so. So we wanted a more general ‘solution’, and i found this post which discusses compressing the ViewState before you send it to the client and decompressing it when the client sends it back. We used pretty much the same approach, but with some differences.

First of all, ViewState is persisted in the resulting HTML page through an IStateFormatter object. We’ll provide our own CompressedStateFormatter which implements the IStateFormatter interface, and uses the standard IStateFormatter that ASP.NET uses:

    public class CompressedStateFormatter : IStateFormatter

    {

        private readonly IStateFormatter actualFormatter;

 

        public CompressedStateFormatter(IStateFormatter actualFormatter)

        {

            this.actualFormatter = actualFormatter;

        }

 

        public string Serialize(object state)

        {

            string decompressedState = actualFormatter.Serialize(state);

 

            using (MemoryStream memoryStream = new MemoryStream())

            {

                using (Stream zipStream = new GZipStream(memoryStream, CompressionMode.Compress))

                using (StreamWriter writer = new StreamWriter(zipStream))

                {

                    writer.Write(decompressedState);

                }

 

                return Convert.ToBase64String(memoryStream.ToArray());

            }

        }

 

        public object Deserialize(string serializedState)

        {

            byte[] data = Convert.FromBase64String(serializedState);

 

            using (MemoryStream memoryStream = new MemoryStream(data))

            using (Stream zippedStream = new GZipStream(memoryStream, CompressionMode.Decompress))

            using (StreamReader reader = new StreamReader(zippedStream))

            {

                return actualFormatter.Deserialize(reader.ReadToEnd());

            }

        }

    }

The idea is very simple: when the Serialize method is called, we first call the real formatter’s Serialize method, compress its return value and then return the Base64-encoded string of the compressed serialized state. And in the Deserialize method, we do the exact opposite: we first decompress the Base64-encoded string and then we use the real formatter to deserialize the actual ViewState.

In Mamanze’s example, he checks to see if the compressed version is actually smaller than the decompressed version and if so, uses the decompressed version instead of the compressed one. And when decompressing he first checks to see if it’s a compressed or decompressed version and obviously only decompresses in case of a compressed version. The only page where i found the compressed version of the ViewState to be larger than the decompressed version was in our log in page, so i just got rid of that piece of the code.

Now we still have to plug this into ASP.NET’s behavior somehow… first we add a pagestate.browser file to the App_Browsers folder of your web application (if it doesn’t exist, just create it) with the following content:

<browsers>

  <browser refID="Default">

    <controlAdapters>

      <adapter controlType="System.Web.UI.Page" adapterType="Our.Application.CompressedPageStateAdapter" />

    </controlAdapters>

  </browser>

</browsers>

The CompressedPageStateAdapter looks like this:

    public class CompressedPageStateAdapter : PageAdapter

    {

        public override PageStatePersister GetStatePersister()

        {

            return new CompressedHiddenFieldPageStatePersister(Page);

        }

    }

And the CompressedHiddenFieldPageStatePersister class looks like this:

    public class CompressedHiddenFieldPageStatePersister : HiddenFieldPageStatePersister

    {

        public CompressedHiddenFieldPageStatePersister(Page page) : base(page)

        {

            FieldInfo field = typeof(PageStatePersister).GetField("_stateFormatter", BindingFlags.NonPublic | BindingFlags.Instance);

            // retrieving this property instantiates the default IStateFormatter

            var defaultFormatter = base.StateFormatter;

            var formatter = new CompressedStateFormatter(defaultFormatter);

            field.SetValue(this, formatter);

        }

    }

The HiddenFieldPageStatePersister is the class that ASP.NET WebForms will use by default to store your ViewState into a hidden field in the resulting HTML. By default, the HiddenFieldPageStatePersister uses the default IStateFormatter type that ASP.NET uses, which only uses Base64 encoding but no compression. Unfortunately, there is no clean way to instruct ASP.NET to use a different implementation for IStateFormatter, so we need to use a bit of reflection to overwrite the value of HiddenFieldPageStatePersister’s _stateFormatter field. Luckily, this also enables us to first get the value of the StateFormatter property so we can pass this reference (which is the ‘real’ formatter) to our CompressedStateFormatter.

And that is all there is to it… all of your pages will now use this CompressedHiddenFieldPageStatePersister so you get the benefit of ViewState compression in each of your pages. You can also do this selectively if you want, by not using the pagestate.browser file and overriding the PageStatePersister property of your ASPX page:

        private CompressedHiddenFieldPageStatePersister persister;

 

        protected override PageStatePersister PageStatePersister

        {

            get

            {

                if (persister == null)

                {

                    persister = new CompressedHiddenFieldPageStatePersister(this);

                }

 

                return persister;

            }

        }

This way, only the pages that contain this code will use the CompressedHiddenFieldPageStatePersister.

Instead of inheriting from HiddenFieldPageStatePersister, you could also inherit from SessionPageStatePersister. SessionPageStatePersister will store your ViewState in the HttpSessionState, and will only include a little bit of ViewState in your HTML page instead of everything. But you do need to be aware of the fact that using the CompressedStateFormatter when inheriting from SessionPageStatePersister will only result in compressing the little bit of ViewState that is included in the HTML, and not the ViewState that is stored in the HttpSessionState.

In case you’re wondering: why should i use this instead of using typical HTTP compression on the IIS level? I believe it has a couple of advantages to HTTP compression. First of all, AFAIK, HTTP compression does not have any benefit on postbacks. And since ViewState is always posted back to the server, this can make a pretty big difference. Also, with this approach, the client will not have to decompress the entire ViewState (which isn’t used client-side anyway) and the browser doesn’t have to waste time on it in general.

I haven’t used this in production yet, but i will very soon… unless someone knows of a good reason why i shouldn’t ;)

13 Responses to “Reducing ViewState Size”

  1. Reflective Perspective - Chris Alcock » The Morning Brew #424 Says:

    [...] Reducing ViewState Size – Davy Brion shows how you can replace the StateFormatter implementation with your own implementation to give you control over the output of the viewstate of an ASP.NET page, allowing you to do things like compression to reduce size [...]

  2. Phil Pursglove Says:

    If you think that’s good, have a look at the SessionPageStatePersister which holds all your ViewState/ControlState in session on the web server and doesn’t transmit it to the client at all…

  3. Davy Brion Says:

    i actually mentioned the SessionPageStatePersister in the post ;)

    and storing all of the ViewState in the session isn’t very good either… by default, it holds a queue of the last 9 ViewStates per user… if you get a lot of concurrent users, and you have a lot of ViewState, the memory overhead will be pretty big. Especially since the session memory won’t be removed until the session timeout is reached which is about 20 minutes (i think that’s the default setting) _after_ a user’s last action on your site.

  4. John Says:

    Any thought about storing the viewstate in a database? I realize this creates extra round trips to the database for the viewstate on each page, but the round trip should be less then downloading and uploading the viewstate even in compressed form. A cleanup routine could be fired Asynchronously with each request to remove expired viewstates or part of a server maintenance plan.

  5. Davy Brion Says:

    It’s definitely an option, though it might be a bit extreme. I also considered it, but i’d rather not do that unless it’s absolutely necessary.

    I’d do the cleanup in some maintenance plan instead of some asynchronous event though

  6. Shou Takenaka Says:

    If you use LoadPageStateFromPersistenceMedium() and SavePageStateToPersistenceMedium(), I think your implementation would be simpler (e.g. How To: Compress ViewState in ASP.NET 2.0).

    Do these method not meet your needs ?

  7. Jaime Says:

    Is there any advantage of using this method against using gzip at IIS level (since IIS will gzip the html with the viewstate in it). Are the advantages in using both?

  8. Davy Brion Says:

    @Shou

    i didn’t know about those methods, and they certainly seem like a fine solution :)

  9. Davy Brion Says:

    @Jaime

    search the post for “In case you’re wondering: why should i use this instead of using typical HTTP compression on the IIS level?” ;)

  10. Luke Says:

    I tried this out on a test version of a production site and I’ve yet to find a page that has a smaller viewstate compressed than uncompressed…

    I got closer to the normal viewstate size by using a binary formatter and gzip compression, but it was still larger than uncompressed. Am I doing something wrong or are other people finding this too? Its an ASP.NET 2 site.

  11. Davy Brion Says:

    @Luke

    very weird… in our case, only one page had a bigger compressed viewstate than uncompressed and it was our LogIn page (which hardly has any viewstate). In other pages, we noticed huge differences, some minor, some moderate, etc…

  12. Erik O'Leary Says:

    FYI, I was able to make the compression ratio even better (yours reduced my pages Viewstate from 31% to 13%, but I was able to reduce it to 6-7%) by simply refactoring your code a little bit and removing the unnecessary streamreaders and streamwriters (GZipSteam apparently doesn’t like them!).

  13. Larry Says:

    I realize that this post is old, but if it is still being monitored, can someone point me in the right direction to overcome the authors observation: ‘But you do need to be aware of the fact that using the CompressedStateFormatter when inheriting from SessionPageStatePersister will only result in compressing the little bit of ViewState that is included in the HTML, and not the ViewState that is stored in the HttpSessionState’. I want to compress the data that is saved in session. Thx.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>