Click here to Skip to main content
Click here to Skip to main content

ViewState Compression

By , 10 Jul 2006
 

Introduction

Recently, I developed a huge ASP.NET page, with more than 30 controls. As we all know, it's a good idea to disable the ViewState for the controls that don't actually need it, say Literals or Labels. After doing that, I noticed that the hidden ViewState field was still a few KBs big. This is obviously a big problem for the users that still don't have a broadband connection, because uploading 40 KB to the server is really a bad issue, especially when they begin to click the "Submit" button again and again because they don't notice any response. So, after a few searches through the Internet, I built a simple solution to compress the ViewState and therefore save a rough 50% of the bandwidth. This post by Scott Hanselman has been particularly useful. Although it's possible to use external libraries to perform compression tasks, I think the better solution is to use the GZipStream or DeflateStream that the .NET Framework 2.0 includes.

Compressing and Decompressing Data in Memory

First of all, we need a way to compress and decompress an array of bytes in memory. I put together this simple static class that exposes two methods: Compress and Decompress. The two available classes, GZipStream and DeflateStream, according to MSDN, use the same algorithm, so it's irrelevant which one you choose.

The code below is really simple, and doesn't need further explanation:

using System.IO;
using System.IO.Compression;

public static class Compressor {

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

You need to save this class in a .cs file and put it in the App_Code directory of your ASP.NET application, making sure it's contained in the proper custom namespace (if you don't specify any namespace, the class will be available in the built-in ASP namespace).

Compressing the ViewState

Now, we can actually compress the ViewState of the page. To do that, we have to override the two methods LoadPageStateFromPersistenceMedium and SavePageStateToPersistenceMedium. The code simply uses an additional hidden field, __VSTATE, to store the compressed ViewState. As you can see, by viewing the HTML of the page, the __VIEWSTATE field is empty, while our __VSTATE field contains the compressed ViewState, encoded in Base64. Let's see the code.

public partial class MyPage : System.Web.UI.Page {

  protected override object LoadPageStateFromPersistenceMedium() {
    string viewState = Request.Form["__VSTATE"];
    byte[] bytes = Convert.FromBase64String(viewState);
    bytes = Compressor.Decompress(bytes);
    LosFormatter formatter = new LosFormatter();
    return formatter.Deserialize(Convert.ToBase64String(bytes));
  }

  protected override void SavePageStateToPersistenceMedium(object viewState) {
    LosFormatter formatter = new LosFormatter();
    StringWriter writer = new StringWriter();
    formatter.Serialize(writer, viewState);
    string viewStateString = writer.ToString();
    byte[] bytes = Convert.FromBase64String(viewStateString);
    bytes = Compressor.Compress(bytes);
    ClientScript.RegisterHiddenField("__VSTATE", Convert.ToBase64String(bytes));
  }

  // The rest of your code here...
}

In the first method, we just decode from Base64, decompress and deserialize the content of the __VSTATE, and return it to the runtime. In the second method, we perform the opposite operation: serialize, compress, and encode in Base64. The Base64 string is then saved into the __VSTATE hidden field. The LosFormatter object performs the serialization and deserialization tasks.

You may also want to create a new class, for example, CompressedPage, inheriting from System.Web.UI.Page, in which you override the two methods and then inherit your page from that class, for example MyPage : CompressedPage. Just remember that .NET has only single inheritance, and by following this way, you "spend" your only inheritance chance to use the ViewState compression. On the other hand, overriding the two methods in every class is a waste of time, so you have to choose the way that best fits your needs.

Performances and Conclusions

After a few tests, I noticed that the ViewState has been reduced from 38 KB to 17 KB, saving 44%. Supposing you have an average of 1 postback per minute per user, you could save more than 885 MB of bandwidth per month on every single user. That's an excellent result: you save bandwidth (and therefore money), and the user notices a shorter server response time.

I wanted to point out that this solution has a performance hit on the server's hardware. Compressing, decompressing, encoding, and decoding data is quite a heavy work for the server, so you have to balance the number of users with your CPU power and RAM.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Dario Solera
Software Developer Aruba SpA
Italy Italy
Member
Software engineer at Aruba SpA (Italy), working on IaaS cloud computing. Cloud believer, (former) entrepreneur, F1 addict.
 
Follow me at dariosolera.it or on Twitter.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberamadou daffe5 Dec '12 - 7:56 
Very good
QuestionNot sure that problem exists in a first placememberGeorge20142 Sep '12 - 19:05 
IIS support the compression and all browsers support it as well.
When you see your ViewState was originally 38Kb you got to remember that you see it AFTER decompression by the browser.
 
So the actual amount of data that was sent from IIS to the browser would be 17Kb (what you saw after your code ran).
 
Bottom line your code will only decrease performance since you doing compression twice on those 38Kb. First your code then IIS will do it.
 
But you are not saving any bandwidth.
Just my 2 cents.
George.
AnswerRe: Not sure that problem exists in a first placememberboros2419 Sep '12 - 6:30 
HTTP compression only works from the server to the client.
When you do a postback, you send data to the server (including the viewstate) uncompressed, so you are saving bandwidth.
AnswerRe: Not sure that problem exists in a first placememberamit@123456710 Dec '12 - 21:45 
I am getting same problem,
 
After using following code for compression,
 
using System.IO;
using System.IO.Compression;
///
/// Summary description for Compressor
///

public static class Compressor
{
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();
}
}
 

 
protected override object LoadPageStateFromPersistenceMedium()
{
string viewState = Request.Form["__VSTATE"];
byte[] bytes = Convert.FromBase64String(viewState);
bytes = Compressor.Decompress(bytes);
LosFormatter formatter = new LosFormatter();
return formatter.Deserialize(Convert.ToBase64String(bytes));
}
 
protected override void SavePageStateToPersistenceMedium(object viewState)
{
LosFormatter formatter = new LosFormatter();
StringWriter writer = new StringWriter();
formatter.Serialize(writer, viewState);
string viewStateString = writer.ToString();
byte[] bytes = Convert.FromBase64String(viewStateString);
bytes = Compressor.Compress(bytes);
ClientScript.RegisterHiddenField("__VSTATE", Convert.ToBase64String(bytes));
}
 
When I click on next button my application gets stopped.
 
Please help me what should i do?
 

Regards,
Amit
GeneralMy vote of 5memberCarlos Mattos (MVP)14 Aug '12 - 9:18 
Great article! Significant performance improvement! Simple, praticle and direct to the point! Congratz!
QuestionNaming & performancememberMalay Thakershi1 Aug '12 - 12:43 
Why do you name the field "__VSTATE"?
 
Are there cases in which savings in viewstate size may become performance bottleneck?
 
Thanks.
GeneralMy vote of 5memberlunabi668 Feb '11 - 2:27 
It saves me a lot of time!
Thank you very!!!
Luisa
GeneralMy vote of 5memberE.F. Nijboer22 Oct '10 - 12:34 
very nice!
QuestionCompression generates even bigger viewstate, why?memberjrbosch4 Feb '10 - 12:21 
This is the result of the viewstate in my application:
 
Using your example:
H4sIAAAAAAAEAO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3mkuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrIHz9+fB8/Iv7vX/M3/e1+zV/3F/z+v/9JtWzrqmxe5b9oXdT5y6ppn2TTt79Xfv37//6/za/56/7YyTyfvn1Svdv9TX/dH/v03sHuzt6Dg3uz/wcl/rgoPwAAAA=="
 
Without using your example, same webform:
="/wEPDwUJNjM4MTAyNzgzZBgBBR5fX0NvbnRyb2xzUmVxdWlyZVBvc3RCYWNrS2V5X18WAQUJQ2hlY2tCb3gxeC4oWoGK+hZjCjTZmpgxFgH6MxQ=
 
¿Am I doing something wrong?
AnswerRe: Compression generates even bigger viewstate, why?memberShadowDanser16 Jan '11 - 3:13 
I have the same result: my viewstate is bigger with compression than without.
GeneralOptimize codemembertruongpham17 Oct '09 - 7:07 
I came across this article. It's a great article. Thanks for sharing.
I tried to play around with sample a bit. I found out that ViewState is really compressed if data is long enough. There are many times the size of ViewState data is grown after compression. For short data, the compression is not effeciently.
 
Here's my suggestions for optimizing codesnippets. First we insert a header (1-byte size) before the first byte of ViewState. This header tells whether ViewState data is compressed or not.
 
protected override void SavePageStateToPersistenceMedium(object viewState)
{
...
bytes = Compressor.Compress(bytes);
 
//NEW CODESNIPPET:
byte[] tmp = Compressor.Compress(bytes);
 
//If size is really smaller than the original one after compression, the compressed array is preferable
if (tmp.Length < bytes.Length)
{
byte[] tmp2 = new byte[tmp.Length + 1];
System.Buffer.BlockCopy(tmp, 0, tmp2, 1, tmp.Length);
tmp2[0] = 1; //Compressed. Save into the first byte
 
bytes = tmp2;
}
else
{
byte[] tmp2 = bytes;
byte[] tmp3 = new byte[bytes.Length + 1];
System.Buffer.BlockCopy(tmp2, 0, tmp3, 1, tmp2.Length);
tmp3[0] = 0; //Not Compressed. Save into the first byte
 
bytes = tmp3;
}
ClientScript.RegisterHiddenField("__VSTATE", Convert.ToBase64String(bytes));
}
 
protected override object LoadPageStateFromPersistenceMedium()
{
byte header = bytes[0]; //Extract data of the first byte. This data tells whether ViewState has been compressed or not previously
 
byte[] tmp = new byte[bytes.Length - 1];
System.Buffer.BlockCopy(bytes, 1, tmp, 0, bytes.Length - 1);
 
bytes = tmp;
 
if (header == 1)
bytes = Compressor.Decompress(bytes);
 
LosFormatter formatter = new LosFormatter();
return formatter.Deserialize(Convert.ToBase64String(bytes));
}
 
Is this codesnippet good?
GeneralRe: Optimize codememberE.F. Nijboer22 Oct '10 - 12:33 
First of all the default compression of gzip isn't very good. Using a more efficient compression method could eliminate this problem and have a higher compression ratio.
Second, your optimization would optimize for size but not for speed. My idea would be that instead of doing the actual compressing and then compare the size with the size of the uncompressed viewstate, the code would be faster to define a threshold that will generally mean that below that size threshold, compression isn't effective and should be skipped. so, if the size of the view state is for example smaller than 512 bytes then skip compression, otherwise compress it.
 
This way it would generally optimize for size without losing speed Big Grin | :-D
PURPOSE: Delays program execution until designated condition is indicated.

QuestionExcellent!!! but have some problems with UserControlsmemberinharry17 Sep '09 - 10:33 
Hi!!
(please excuse my english).
Thanks for the code, works 99% ok!
Also changed the ScriptManager.RegisterHiddenField like some poster adviced.
 
But i have some problems with usercontrols (.ascx) inside UpdatePanel.
My usercontrols have dropdown combos and i notice the the contains of the combos dissaperar.
 
Any advice?
Thanks in advance.
Best regards,
Homero Gonzalez.
GeneralSuperb!memberkjerolran5 Mar '09 - 10:23 
Actually the Viewstate it's a great tool for Developing web App. but it can turn out to be a Performance trouble if it grows too much, your article it's a smooth workaround. Thanks for sharing it.
it also reduces the chance of getting the horrible MAC problem.
 
Great!
Generaldoubt in above articlememberMaha_M15 Jun '08 - 5:40 
The magic number in GZip header is not correct. Make sure you are passing in a GZip stream
 
I got this error while in this line
read = gzip.Read(buff, 0, buff.Length);
 
pls help me out soon.
 
Maharajan,
software engineer,
Kom7 technologies,
India..

QuestionMemory leak?memberlrwilson4 Jun '08 - 3:59 
This is a great solution. I use the viewstate a lot, and even though I disable it where I can, I still get a viewstate larger than I would like.
 
Consider this modification:
 
public static class Compressor
{
 
public static byte[] Compress(byte[] data)
{
using (MemoryStream output = new MemoryStream())
{
using (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)
{
using (MemoryStream input = new MemoryStream())
{
input.Write(data, 0, data.Length);
input.Position = 0;
using(GZipStream gzip = new GZipStream(input, CompressionMode.Decompress, true))
{
using (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();
}
}
}
}
}

 
it's exactly the same as before, but since all the objects used are IDisposable, I've initiated them with "using()".
 
Here's why:
 
http://www.angrycoder.com/article.aspx?ArticleID=336[^]
 
I think there is a danger with the original code for memory leaks, because the objects are not disposed of.
 
What do you think?
AnswerRe: Memory leak?memberDario Solera7 Jun '08 - 6:12 
Given that the streams I used here are always managed in RAM, I supposed that the use of using could be avoided because the objects are disposed at the end of their scope (I hope). At any rate, it's surely a good practice to use using.
 
If you truly believe you need to pick a mobile phone that "says something" about your personality, don't bother. You don't have a personality. A mental illness, maybe - but not a personality. - Charlie Brooker
My Photos/CP Flickr Group - ScrewTurn Wiki

GeneralCompression and Ajaxmembermdmasonmbcs14 Apr '08 - 5:22 
Good Article! Great idea!.
 
I 've attempted to use this in an ASPNet 2.0 web application with AJAX 1.0 and found I get an ObjectDisposedException after selecting a grid row. I've used the correct ScriptManager reference for registering the hidden field. For some reason the stream is closed prematurely. The stack trace is
at System.IO.__Error.StreamIsClosed()
at System.IO.MemoryStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.Compression.DeflateStream.Read(Byte[] array, Int32 offset, Int32 count)
at System.IO.Compression.GZipStream.Read(Byte[] array, Int32 offset, Int32 count)

the error occurs in the DecompressViewState in the ViewStateCompressor.cs is
static byte[] DecompressViewState( byte[] compData)
{
.....
byteRead = gzip.Read(buf, 0, buf.Length);
.....
}

I'm trying to fathom the reason for the exception.
GeneralRe: Compression and AjaxmemberTim McCurdy14 Jan '09 - 2:53 
That's because you haven't taken into account "AsyncPostBacks". During every PostBack you need to Compress / Decompress the ViewState. This needs special handling during an AsyncPostBack where MS Ajax is involved. If you're using a differt Ajax Framework, sorry, I can't help you, but it's the same concept. You don't need to modify you Compress / Decompress routines, just the Restore/Save PageStateToPersistanceMedium.
 
Notice the bolded text in the "SavePageStateToPersistenceMedium" method. During an AsyncPostBack you need to re-register the HiddenField, however, you cannot simply just do this the same way. If you're in an AsyncPostBack, you need to use the ScriptManager methods to do this. If you're just in the first PageLoad or subsequent PostBacks, then using the ClientScript is fine.
 
/// <summary>
/// Loads the ViewState from the persistence medium
/// </summary>
protected override object LoadPageStateFromPersistenceMedium()
{
	try {
		string compression = ConfigurationManager.AppSettings["viewStateCompression"];
		if (string.IsNullOrEmpty(compression)) compression = "true";
		if (bool.Parse(compression)) {
			string viewState = Request.Form["__VSTATE"];
			if (viewState.EndsWith(",")) viewState = viewState.Substring(0, viewState.Length - 1);
			byte[] bytes = Convert.FromBase64String(viewState);
					bytes = BasePage.Decompress(bytes);
 
			viewState = Convert.ToBase64String(bytes);
			if (string.IsNullOrEmpty(viewState)) return null;
			LosFormatter formatter = new LosFormatter();
			return formatter.Deserialize(viewState);
		}
		else
			return base.LoadPageStateFromPersistenceMedium();
	}
	catch (Exception) { throw; }
}
 
/// <summary>
/// Saves the ViewState from the persistence medium
/// </summary>
protected override void SavePageStateToPersistenceMedium(object state)
{
	StringWriter writer = new StringWriter();
	try {
		string compression = ConfigurationManager.AppSettings["viewStateCompression"];
		if (string.IsNullOrEmpty(compression)) compression = "true";
		if (bool.Parse(compression)) {
			LosFormatter formatter = new LosFormatter();
			formatter.Serialize(writer, state);
			string vState = writer.ToString();
			byte[] bytes = Convert.FromBase64String(vState);
			bytes = BasePage.Compress(bytes);
			vState = Convert.ToBase64String(bytes);
 
			System.Web.UI.ScriptManager sm = System.Web.UI.ScriptManager.GetCurrent(this);
			if (sm != null && sm.IsInAsyncPostBack)
				System.Web.UI.ScriptManager.RegisterHiddenField(this, "__VSTATE", vState);
			else
				Page.ClientScript.RegisterHiddenField("__VSTATE", vState);
		}
		else
			base.SavePageStateToPersistenceMedium(state);
	}
	catch (Exception) { throw; }
	finally {
		if (writer != null) { writer.Dispose(); writer = null; }
	}
}

GeneralRe: Compression and AjaxmemberMninawa18 Aug '10 - 3:25 
You saved my Butt today, you code works perfectly, i didnt consider the ajax part..
Im using radcontrol to initiate ajax in my application.
From
M.Paige
GeneralRe: Compression and AjaxmemberTim McCurdy18 Aug '10 - 3:59 
Glad to help. I do have a small update though. There is no need to ever call Page.ClientScript if you have a ScriptManager because internally IT knows if it's within an AsyncPostBack and uses the ClientScript internally if needed, so you can change all code that does this:
 

System.Web.UI.ScriptManager sm = System.Web.UI.ScriptManager.GetCurrent(this);
if (sm != null && sm.IsInAsyncPostBack)
     System.Web.UI.ScriptManager.RegisterHiddenField(this, "__VSTATE", vState);
else
     Page.ClientScript.RegisterHiddenField("__VSTATE", vState);
 
To this (notice I am not checking if the current request is Async):
System.Web.UI.ScriptManager.RegisterHiddenField(...);
or
System.Web.UI.ScriptManager.RegisterScriptBlock(...);
or
System.Web.UI.ScriptManager.RegisterScript(...);
Generalgood stuffmembermrkyle28 Feb '08 - 18:39 
can this be done for the hidden field __EVENTVALIDATION also?
GeneralExcellent Article..!!memberSujith John Thomas21 Jul '07 - 2:18 
Any other known issues with this technic?
GeneralAnother way [modified]memberboros2422 Feb '07 - 3:13 
Hi, nice code.
I used it but i have some problems with it, (some controls loses the viewstate).
I found a better way apply the same idea:
Create a new class that inherits from PageStatePersister
and overrides the PageStatePersister property of the page.
here is the class i have made:
 
Public Class VSCompressor
    Inherits PageStatePersister
    Private _stateFormatter As LosFormatter
    Protected Shadows ReadOnly Property StateFormatter() As LosFormatter
        Get
            If Me._stateFormatter Is Nothing Then
                Me._stateFormatter = New LosFormatter
            End If
            Return Me._stateFormatter
        End Get
    End Property
    Public Sub New(ByVal page As Page)
        MyBase.New(page)
    End Sub
    Public Overrides Sub Load()
        Dim bytes() As Byte = Convert.FromBase64String(MyBase.Page.Request.Form("__VSTATE"))
        Dim input As New MemoryStream
        input.Write(bytes, 0, bytes.Length)
        input.Position = 0
        Dim gzip As New GZipStream(input, CompressionMode.Decompress, True)
        Dim output As New MemoryStream
        Dim buff(64) As Byte
        Dim read As Integer = -1
        read = gzip.Read(buff, 0, buff.Length)
        While read > 0
            output.Write(buff, 0, read)
            read = gzip.Read(buff, 0, buff.Length)
        End While
        gzip.Close()
        gzip.Dispose()
        Dim p As Pair = DirectCast(StateFormatter.Deserialize(Convert.ToBase64String(output.ToArray)), Pair)
        MyBase.ViewState = p.First
        MyBase.ControlState = p.Second
    End Sub
    Public Overrides Sub Save()
        Dim writer As New StringWriter
        StateFormatter.Serialize(writer, New Pair(MyBase.ViewState, MyBase.ControlState))
        Dim bytes() As Byte = Convert.FromBase64String(writer.ToString)
        Dim output As New MemoryStream
        Dim gzip As New GZipStream(output, CompressionMode.Compress, True)
        gzip.Write(bytes, 0, bytes.Length)
        gzip.Close()
        gzip.Dispose()
        MyBase.Page.ClientScript.RegisterHiddenField("__VSTATE", Convert.ToBase64String(output.ToArray))
    End Sub
End Class
 
this is the code you should put in the page:
 
Private _persister As New VSCompressor(Me)
    Protected Overrides ReadOnly Property PageStatePersister() As PageStatePersister
        Get
            Return _persister
        End Get
    End Property
ing it the original size was 900kb
after compress it with your code was 300kb, and now is 150kb

GeneralRe: Another waymemberMunsifali Rashid28 Oct '07 - 11:02 
Great solution. Worked perfectly, except to work with ajax postbacks please change the following line (as recommended by another poster):
 
MyBase.Page.ClientScript.RegisterHiddenField("__VSTATE", Convert.ToBase64String(output.ToArray))
 
With this:
 
ScriptManager.RegisterHiddenField(Page, "__VSTATE", Convert.ToBase64String(output.ToArray))
 
Otherwise, the viewstate doesn't get updated and can cause some very strange behaviour (as I learned the hard way!).
 
Cheers,
 
Mun
 
--
Munsifali Rashid
www.munit.co.uk/

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 10 Jul 2006
Article Copyright 2006 by Dario Solera
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid