Wednesday 30 April 2008

Validation of viewstate MAC failed error - Fixed

There is a problem with ASP.NET encrypted viewstate which means it will generate an error if your page is slow to load and the user tries to post back to the page (e.g. by clicking a button). Encrypted viewstate typically exists on the page because any controls (like GridViews) that use "DataKeyNames" require it. The big problem is that it is put at the bottom of the page just before the end form tag - which may not have been loaded by the time a user post backs on a slow-loading page. This often happens on the first hit to a page as the client is downloading images or the JIT compiler is doing its work.

One of the best articles I've seen on some of the solutions to this problem is here:

http://blogs.msdn.com/tom/archive/2008/03/14/validation-of-viewstate-mac-failed-error.aspx

There are 3 workarounds suggested:
  1. Set enableEventValidation to false and viewStateEncryptionMode to Never.
  2. Disable the form until the page has finished loading
    function enableForm() {
    document.getElementById("form").disabled = false;
    }
    window.onLoad = enableForm();


  3. Or override the Render of your page so that the Encrypted section of the viewstate is put at the top of the form rather than at the bottom as it is by default. I've tested this version in our application which uses Virtual Earth maps and it fixes the issues with have with slow page loads (RECOMMENDED as it does not rely on javascript to work):




public abstract class BasePage : Framework.Web.UI.Pages.BasePage
{
/// <summary>
/// Fix for viewstate issue when the user clicks on a button before the encrypted section
/// of the viewstate is finished.
/// As per fix in
/// http://blogs.msdn.com/tom/archive/2008/03/14/validation-of-viewstate-mac-failed-error.aspx
/// </summary>
/// <param name="writer"></param>
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
System.IO.StringWriter stringWriter = new System.IO.StringWriter();
HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);
base.Render(htmlWriter);
string html = stringWriter.ToString();
string[] aspnet_formelems = new string[5];
aspnet_formelems[0] = "__EVENTTARGET";
aspnet_formelems[1] = "__EVENTARGUMENT";
aspnet_formelems[2] = "__VIEWSTATE";
aspnet_formelems[3] = "__EVENTVALIDATION"; aspnet_formelems[4] = "__VIEWSTATEENCRYPTED";
foreach (string elem in aspnet_formelems)
{
//Response.Write("input type=""hidden"" name=""" & abc.ToString & """")
int StartPoint = html.IndexOf("<input type=\"hidden\" name=\"" +
elem.ToString() + "\"");
if (StartPoint >= 0)
{
//does __VIEWSTATE exist?
int EndPoint = html.IndexOf("/>", StartPoint) + 2;
string ViewStateInput = html.Substring(StartPoint, EndPoint - StartPoint);
html = html.Remove(StartPoint, EndPoint - StartPoint);
int FormStart = html.IndexOf("<form");
int EndForm = html.IndexOf(">", FormStart) + 1;
if (EndForm >= 0)
html = html.Insert(EndForm, ViewStateInput);
}
}

writer.Write(html);
}

No comments: