|
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));
}
}
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.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 63 (Total in Forum: 63) (Refresh) | FirstPrevNext |
|
 |
|
|
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..
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
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?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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.
|
| Sign In·View Thread·PermaLink | 2.00/5 (2 votes) |
|
|
|
 |
|
|
 |
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Very nice solution!
Using the compression with AJAX 1.0, there is a problem because the viewstate is not updated in partial rendering (with UpdatePanel).
The solution is to replace the line
ClientScript.RegisterHiddenField("__VSTATE", Convert.ToBase64String(bytes)); with this code:
ScriptManager.RegisterHiddenField(this, "__VSTATE", Convert.ToBase64String(bytes));
With this simple change all works fine with Ajax too...
|
| Sign In·View Thread·PermaLink | 3.92/5 (6 votes) |
|
|
|
 |
|
|
 |
|
|
You saved my life! I was about to go crazy with the controls and their viewstate problems. Thank you very much!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
in my option every request from client browser do zip every postback from client browser do dezip the html was became short and small but the server will be busy. cpu will be tied.
|
| Sign In·View Thread·PermaLink | 2.00/5 (4 votes) |
|
|
|
 |
|
|
I tried he code and it is great..! But i just managed to reduce a few bytes of a page. The page on wich i tried the code contains DataList control.
any other way to reduce the size further.
Thanks. good Job.
Deepak Surana
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
why not just use server side viewstate? You save all the bandwith... and likelyt the cost of storing the state somewhere is less then compressing, sending, recieving and decompressing.... maybe when i have time(not likley) ill run some performance tests. If you are using 2.0, server side viewstate is super easy, in 1.1, it's slightly more involved, but still easy enough.
http://www.jonavi.com
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Yes, you can store the ViewState on the server, using the Session object, but I don't believe that's a good solution when you have many users. In my case I needed to compress the ViewState only in one page, so it's not a problem to compress and decompress it.
_____________________________________________ Tozzi is right: Gaia is getting rid of us. My Blog [ITA] - Developing ScrewTurn Wiki 1.0 Beta3
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
why use session? you can use DB, text file, whatever you want... jsut write an adapter for it. It works well for many users  Agreed, the nice thing about your solution is the per page option, with 2.0 page adapters you have it for the app or not. What we do where I code most the time, is most pages/sections of the site are their own apps/virtual direcotries anyway. In any case, I like the article, code, and idea.
|
| Sign In·View Thread·PermaLink | 3.50/5 (2 votes) |
|
|
|
 |
|
|
jonavi wrote: why use session? you can use DB, text file, whatever you want
I don't think so. A DB is surely slower than in-memory compression, and using files is even worse because you would have to set a file for every user... I think using Session it's the only way other than compression. Moreover, using a DB or files introduces the new problem of managing expiration; on the other hand using Session would waste memory when the user goes away and the session expires after (default) 20 mins. If you have manhy "occasional" users you end up having hundreds open sessions but only a few of them are still used, wasting lots of memory.
jonavi wrote: In any case, I like the article, code, and idea.
Thanks.
_____________________________________________ Tozzi is right: Gaia is getting rid of us. My Blog [ITA] - Developing ScrewTurn Wiki 1.0 Beta3
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Question... since I have been looking at this a bit more, and mostly I am interested in the compression aspect 
Do you know if it's possible, when compressing something with GZip the way you did. 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(); }
Is it possible to decompress that data using Javascript?
My idea being that the method would be quite beneficial when mixed with using for example a server control that has some ajax uses. Think paging grid or something, where the return string itself could be rather large. So, passing the compressed string to the client to be handled, with no .net framework, and just a browser, will he be able to decompress the data and use it?
http://www.jonavi.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
jonavi wrote: Is it possible to decompress that data using Javascript?
I don't know, but I think that GZip works at bit-level so it would be impossible or highly difficult to decompress a GZip stream using JavaScript. You may implement a simple compression algorithm and then decompress the data through JavaScript. You may consider RLE, but it doens't have high compression rates at all.
If you have big responses you should consider HTTP compression. I have written an article about it: Link. It uses GZipStream and it's really simple to implement.
_____________________________________________ Tozzi is right: Gaia is getting rid of us. My Blog [ITA] - Developing ScrewTurn Wiki 1.0 Beta3
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
What sort of equation are you doing for the perf test?
I want to test my results a bit, so lets say...
String state = "wahtever long string"
int startLength = state.Length;
bytes = Compressor.Compress(bytes);
int endLength = bytes.Length;
Double perc = (endLength % startLength) x 100;
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I dont think its possible with javasdcript, anyway, i liked the idea of a page by page option so i made this property in my base page. private bool _CompressViewState = false; public bool CompressViewState { get { return _CompressViewState; } set { _CompressViewState = value; } } There is also a global key to turn it on and off globaly, if we see performance is quick enough with it. we will have to test becuase our site has a failry large number of simultaneous users for most the day, so the cost of compression decompression on the server for everything may be to costly, but if our devs are careful and pick the right places, this could save quite a bit.
|
| Sign In·View Thread·PermaLink | 2.00/5 (2 votes) |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|