views:

1047

answers:

4

I'm currently fighting with the .NET Viewstate and it is starting to wear me down. Having found that some of the pages in one of our applications is made up of around 80% viewstate I have looked into reducing this where I can.

I have looked at (and am happy with) disabling viewstate for controls that do not need it (labels, buttons etc) and have made some small gains here.

I'm now looking at viewstate compression and while I can demonstrate a 40-50% decrease in size it does not seem to be playing well with my application.

Scenario: Page contains a few dropdown lists, a button and a Grdiview (hence the need to deal with the ViewState!). When the page loads the DDLs are populated and default selections are made. Pressing the OK button results in the Gridview being populated as expected.

Now the problem: With Viewstate Compression enabled, if the user changes the selected items in the DDLs before clicking the OK button they will get a 'Required Field Validator' error indicating that a selection has not been made in one of the DDLs - but this is not the case! Disabling the compression code removes the problem and the page operates as expected (i.e. as it has for months!).

Could the problem be down to the viewstate now being stored in a key other than __VIEWSTATE [the code that I have seen use different key names - VSTATE for instance).

My page sources look like this;

Page Source with Compression (note the empty __VIEWSTATE key):

<div>
<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
<input type="hidden" name="__LASTFOCUS" id="__LASTFOCUS" value="" />
<input type="hidden" name="__VSTATE" id="__VSTATE" value="H4sIAAAAAEAO29B2AcSZYlJ
.
.
MKd2afqdaImFR5UiFXVyQPwLPA//8xt+pMsSQ8vlOklcoNgmZfJd8hHvk6/S/7UbxxAJTjzZfp6Qcm039
h3d3dvvPO7/Oa/7i57uemj1H2a/gw5lJQ+ySjFRtPZUL7A/3o2ImFR5UiFXVyLPA+38At70F1EkwAAA=" />
<input type="hidden" name="__VIEWSTATE" id="
__VIEWSTATE" value="" />
</div>

Page Source without Compression:

<div>
<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
<input type="hidden" name="__LASTFOCUS" id="__LASTFOCUS" value="" />
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTYxOTM1NDg4N
A9kFgJmD2QWAgIDD2QWAgIFD2QWAmYPZBYKAgEPZBYIAgcPZBYCAgMPDxYCHgRUZXh0BRdEYXduQyBbY2hhbm
dlIHBhc3N3b3JkXWRkAgkPFgIeB1Zpc2libGVoFgQCAQ8PFgIfAAUFQWRtaW5kZAIDDw8WAh8ABQUxNDoyNGR
.
.
.
.
.
.
kAgsPDxYEHwAFWVNlbGVjdGVkIFNlcnZpY2UgVXNlcjogPGEgY2xhc3M9J3N1U2VsZWN0b3InIGhyZWY9J2xp
c3RzZXJ2aWNldXNlcnMuYXNweCc+PGI+bm9uZTwvYj48L2E+HwFoZGQCDw8QZGQWAGQCBQ8UKwADZDwrABQEA
BYSHg9QYXJlbnRJdGVtQ2xhc3MFC2lnbW5fUGFyZW50HhdUb3BMZXZlbFBhcmVudEl0ZW1DbGFzcwUTaWdtbl
Ub3BFBhcmVudB4KSlNGaWxlTmFtZWUeFlRvcExldmVsSG92ZXJJdGVtQ2xhc3MFNGlnbW5fVG9wTGV"     
/>
</div>

How does .NET know where the VIEWSTATE is stored and does it know that i have moved it?

Do I need to make any other changes to my code apart from implementing SavePageStateToPersistenceMedium and LoadPageStateFromPersistenceMedium?

Edit: I've got to the bottom of this - well almost, I have it working! For some reason using the ClientScript.RegisterHiddenField method appears to have been the source of the problem. Modifying the code to utilise the base class save method, i.e. base.SavePageStateToPersistenceMedium(compressedBytes) and processing the Pair object returned by base.LoadPageStateFromPersistenceMedium() I now have a working solution. Test show a reduction of aroun 70% on the test page so I'm pretty happy with that.

Now that I have compression working I need to recommend that the next (first proper) Code Review has a focus on removing viewstate where it is not needed.

Thanks to eveyone who responded to my cry for help - saved me a few grey hairs there.

+2  A: 

There is a constant hidden in .NET that holds the viewstate field name. We did compression by hooking in the methods you describe but keeping the viewstate name same without issues.

Rashack
I did try keeping the name the same but just got 'Invalid character in a Base-64 string' exceptions for my troubles :-( [Maybe due to errors in the code posted on the web, maybe not]. The code utilised different key names so i assumed that this was why....but it would appear not.
DilbertDave
+2  A: 

hey viewstate compression works for me without any problems. basically i have a baseclass for all my aspx pages which use viewstate. even i am using a different viewstate key to save my viewstate. however you have to override 2 methods for this:

1) - to save viewstate 2) - to load viewstate

as long as you are doing this, you should have no problems. see below code which i am using for my baseclass

using System;
using System.IO;
using System.Web.UI;

namespace XC.UI.WebForms
{

    public class PageBase : System.Web.UI.Page
    {

        protected override object LoadPageStateFromPersistenceMedium()
        {
            string viewState = Request.Form["__VSTATE"];
            byte[] bytes = Convert.FromBase64String(viewState);
            bytes = XC.Common.ViewStateHelper.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 = XC.Common.ViewStateHelper.Compress(bytes);
            ClientScript.RegisterHiddenField("__VSTATE", Convert.ToBase64String(bytes));
        }

    }

}
Raj
The code I have is very similar but doesn't use that LosFormatter (a previous attempt did though but I can't remember why i torn it out and tried again). I'll give it another try because I can't see much else different.
DilbertDave
@dilbertdave - are you making sure that with your current implementation - you are saving AS WELL AS loading viewstate with the same KEY names? that might be the issue?
Raj
yep - overridding SavePageStateToPersistenceMedium and LoadPageStateFromPersistenceMedium but as I pulled this from the web I'm not 100% sure that they are wired up correctly.
DilbertDave
@dave - can u try my code on your aspx and test it? this has been working for me without any problems since quite some time. i think i had taken this from scott hanselmans blog (not quite sure though)
Raj
@Raj - I've replaced my code with yours (minor mods for path to Compress/Decompress - which are in the same base class) but the RequiredField validator is still firing - even though a valid selection has been made. starting to smell like a bug in this [legacy] code :-(
DilbertDave
@dave - very interesting! can u try the code but using the same legacy viewstate key? wonder why i havent got this problem so far!
Raj
@Raj - Just to clarify, I think that there is a bug in our legacy code, not what you have posted. ;-)
DilbertDave
@dave - oh, but i was just wondering how your same code would work with no viewstate compression ;-). i am googling for this as its made me kind of restless ;-)
Raj
@Raj: I think it maybe a problem with "my" Compress/Decompress code
DilbertDave
+1  A: 

This may be overkill, but there is a very cool hardware solution to this problem at www.strangeloop.net. No affiliation, just impressed by the technology.

Back to the page tho - what kinds of controls are generating all this viewstate? Grids? You indicated you went over this already, but do watch out for 'unnecessary runats' with tables, td's, tr's, div's, etc. that have runat="server" set. The entire contents of those controls get persisted in viewstate. We recently chopped 40% off our viewstate payload by recognizing a div was client-side and didn't need runat="server".

n8wrl
There are a few divs, a fieldset and even a couple of <p> elements flagged as RunAt=server so we could strip out a little more. Thanks for the pointer.
DilbertDave
Or you explicitly specify enableViewState="false" for those items.
Rashack
@Rashack: The problem is that I've inherited this application and I'm not 100% sure why the runat tags were added, i.e. if I take them off what am I going to break :-s
DilbertDave
@DilbertDave: Take them off and see! :)
n8wrl
+1  A: 

if you are using ajax in your page please change this line of code. It will solve your problem. ClientScript.RegisterHiddenField("_VSTATE", Convert.ToBase64String(bytes)); replace above statement with ScriptManager. ScriptManager.RegisterHiddenField(this, "_VSTATE", Convert.ToBase64String(bytes));

Satish Varma
Thanks for the feedback - although we have totally revamped this bit of code now.
DilbertDave