Validation of viewstate MAC failed error

-- 3/17/2009 UPDATE --

If you are using .NET 2.0 or later, please upgrade to .NET 3.5 SP1 (.NET 2.0 SP2) as it has a fix in it that should resolve this issue.  If you update and still have the problem, please post here so we know.

-- 3/17/2009 UPDATE --

 

Issue Description

You can get this Exception:

 

 HttpException (0x80004005): Validation of viewstate 
MAC failed. If this application is hosted by a Web 
Farm or cluster, ensure that  configuration 
specifies the same validationKey and validation 
algorithm. AutoGenerate cannot be used in a cluster.

 

when the following preconditions are true:

1. You aren't using a web farm.

2. It appears when using built-in databound controls such as GridView, DetailsView or FormView which utilize “DataKeyNames”.

3. It appears if you have a large page which loads slowly for any reason.

If following preconditions are true and you click a postbacking control/link and the page hasn't loaded completely in client browser, you might get the "Validation of ViewState MAC failed" exception.

When this happens, if you just try setting the page property "EnableViewStateMac" to false, it does not solve the problem, it just changes the error message in same navigation behavior:

 

 The state information is invalid for 
this page and might be corrupted.

 

Other exceptions that seem to be the same problem look like:

 Exception: System.Web.HttpUnhandledException
Message: Exception of type 'System.Web.HttpUnhandledException' was thrown.
Source: System.Web
at System.Web.UI.Page.HandleError(Exception e)
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, 
Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, 
Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest()
at System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context)
at System.Web.UI.Page.ProcessRequest(HttpContext context)
at ASP.test_aspx.ProcessRequest(HttpContext context)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExec
utionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& 
completedSynchronously) 

Nested Exception 

Exception: System.ArgumentException
Message: Invalid postback or callback argument. 
Event validation is enabled using in configuration 
or <%@ Page 
EnableEventValidation="true" %> in a page. For security purposes, this feature 
verifies that arguments to postback or callback 
events originate from the server control that 
originally rendered them. If the data is valid 
and expected, use the 
ClientScriptManager.RegisterForEventValidation 
method in order to register the postback or 
callback data for validation.
Source: System.Web
at System.Web.UI.ClientScriptManager.ValidateEvent(String uniqueId, String 
argument)
at System.Web.UI.Control.ValidateEvent(String uniqueID, String eventArgument)
at System.Web.UI.WebControls.TextBox.LoadPostData(String postDataKey, 
NameValueCollection postCollection)
at System.Web.UI.WebControls.TextBox.System.Web.UI.IPostBackDataHandler.LoadPostData(St
ring postDataKey, NameValueCollection postCollection)
at System.Web.UI.Page.ProcessPostData(NameValueCollection postData, Boolean 
fBeforeLoad)
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, 
Boolean includeStagesAfterAsyncPoint) 

and

 Content-Length:1232
Content-Type:application/x-www-form-urlencoded
Accept:*/*
Accept-Encoding:gzip,deflate
Accept-Language:en-us
Cookie:ASP.NET_SessionId=skk4vriy1lzghm55bicesz45; 
Host:
User-Agent:T-Mobile Dash Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; 
Smartphone; 320x240) UA-OS:Windows CE (Smartphone) - Version 5.1
UA-color:color16
UA-pixels:320x240
x-wap-profile:""
UA-Voice:TRUE
UA-CPU:x86
X-Forwarded-For: 

Thread: 964
Date: 2007-10-25 13:47:03,925
NDC: (null) 

Exception: System.Web.HttpException
Message: Validation of viewstate MAC failed.
 If this application is hosted by a Web 
Farm or cluster, ensure that  configuration 
specifies the same validationKey and validation
algorithm. AutoGenerate cannot be used in a cluster.
Source: System.Web
at System.Web.UI.ViewStateException.ThrowError(Exception inner, String 
persistedState, String errorPageMessage, Boolean macValidationError)
at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString)
at 
System.Web.UI.ObjectStateFormatter.System.Web.UI.IStateFormatter.Deserialize(String 
serializedState)
at System.Web.UI.Util.DeserializeWithAssert(IStateFormatter formatter, String 
serializedState)
at System.Web.UI.HiddenFieldPageStatePersister.Load()
at System.Web.UI.Page.LoadPageStateFromPersistenceMedium()
at System.Web.UI.Page.LoadAllState()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, 
Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, 
Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest()
at System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context)
at System.Web.UI.Page.ProcessRequest(HttpContext context)
at ASP.test_aspx.ProcessRequest(HttpContext context) 
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExec
utionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& 
completedSynchronously) 

Nested Exception 

Exception: System.Web.UI.ViewStateException
Message: Invalid viewstate. 
Client IP: 
Port: 61362
User-Agent: T-Mobile Dash Mozilla/4.0 (compatible; MSIE 4.01; 
Windows CE; Smartphone; 320x240)
ViewState: 
/wEPDwUJMjUyNTI1NDMyD2QWAmYPZBYCAgEPZBYEAlAPD2QWAh4Kb25rZXlwcmVzcwUqcmV0dXJuIGNsaWNr
QnV0dG9uKGV2ZW50LCdjdGwwMF9pYlNlYXJjaCcpZAJWD2QWBAIBD2QWBAIDDw9kFgIfAAUxcmV0dXJuIGNs
aWNrQnV0dG9uKGV2ZW50LCdjdGwwMF9jcDFfRmxhc2hCdXR0b24nKWQCBg8UKwACDxYCHghJbWFnZVVybAUj
fi9faG9tZV9hZF91bml0cy9ob21lX2FkdW5pdHMxMS5qcGdkDxQrAAMWCB4LQ29vcmRpbmF0ZXMFFzAsMCwy
NTUsMCwyMjQsMTEwLDAsMTEwHgtOYXZpZ2F0ZVVybAUGfi9uZmwvHg1BbHRlcm5hdGVUZXh0BSNUaGUgcnVz
aCB0byBGYXRoZWFkIGZvb3RiYWxsIGlzIG9uIR4GVGFyZ2V0ZRYIHwIFGzQ5OCwwLDQ1OCwxMTAsMjI0LDEx
MCwyNTUsMB8DBRB+L2hlcm9lcy9iYXRtYW4vHwQFOVRoZSBEYXJrIEtuaWdodCBpcyBoZXJlLi4uYW5kIGhl
J3MgYnJvdWdodCBzb21lIGZyaWVuZCdzIR8FZRYIHwIFGzcwOCwxMTAsNzM5LDAsNDk4LDAsNDU4LDExMB8D
BQZ+L21sYi8fBAUhTUxCIGhpdHMgYSBob21lIHJ1biB3aXRoIEZhdGhlYWQhHwVlFCsBAwICAgICAmQCAw8P
FgIeB1Zpc2libGVoZBYCAgEPZBYCZg8PZBYCHwAFMnJldHVybiBjbGlja0J1dHRvbihldmVudCwnY3RsMDBf
Y3AxX1N0YXRpY0J1dHRvbicpZBgBBR5f... 

Nested Exception 

Exception: System.Web.HttpException
Message: Unable to validate data.
Source: System.Web
at System.Web.Configuration.MachineKeySection.GetDecodedData(Byte[] buf, Byte[] 
modifier, Int32 start, Int32 length, Int32& dataLength)
at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString) 

 

Root Cause

This exception appears because Controls using DataKeyNames require Viewstate to be encrypted. When Viewstate is encrypted (Default mode, Auto, is to encrypt if controls require that, otherwise not), Page adds <input type="hidden" name="__VIEWSTATEENCRYPTED" id="__VIEWSTATEENCRYPTED" value="" /> field just before closing of the <form> tag. But this hidden field might not have been rendered to the browser with long-running pages, and if you make a postback before it does, the browser initiates postback without this field (in form post collection). End result is that if this field is omitted on postback, the page doesn't know that Viewstate is encrypted and causes the aforementioned Exception. I.E. page expects to be fully-loaded before you make a postback.

And by the way similar problem is with event validation since __EVENTVALIDATION field is also rendered on the end of the form. This is a security feature that ensures that postback actions only come from events allowed and created by the server to help prevent spoofed postbacks. This feature is implemented by having controls register valid events when they render (as in, during their actual Render() methods). The end result is that at the bottom of your rendered <form> tag, you'll see something like this: <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="AEBnx7v.........tS" />. When a postback occurs, ASP.NET uses the values stored in this hidden field to ensure that the button you clicked invokes a valid event. If it's not valid, you get the exception above.

The problem happens specifically when you postback before the EventValidation field has been rendered. If EventValidation is enabled (which it is, by default), but ASP.net doesn't see the hidden field when you postback, you also get the exception. If you submit a form before it has been entirely rendered, then chances are the EventValidation field has not yet been rendered, and thus ASP.NET cannot validate your click.

Workarounds

1. Set enableEventValidation to false and viewStateEncryptionMode to Never as follows:

 <pages enableeventvalidation="false" 
    viewstateencryptionmode="Never">

This has the unwanted side-effect of disabling validation and encryption.  On some sites, this may be ok to do, but it isn't a best practice, especially in publicly facing sites.

 

2. Another way around the problem is to mark the form as disabled and then enable it in script once the load is complete:

   function enableForm() { 
      document.getElementById("form").disabled = false; 
  } 

  window.onLoad = enableForm();

 

Or you can disable the individual form elements and enable them once everything is loaded:

   function disableElements(elements) { 
  for (var i = elements.length - 1; i >= 0; i--) { 
    var elmt = elements[i]; 

    if (!elmt.disabled) { 
      elmt.disabled = true; 
    } 
    else { 
      elmt._wasDisabled = true; 
    } 
  } 
} 

function disableFormElements() { 
  disableElements(_form.getElementsByTagName("INPUT")); 
  disableElements(_form.getElementsByTagName("SELECT")); 
  disableElements(_form.getElementsByTagName("TEXTAREA")); 
  disableElements(_form.getElementsByTagName("BUTTON")); 
  disableElements(_form.getElementsByTagName("A")); 
} 

function enableElements(elements) { 
  for (var i = elements.length - 1; i >= 0; i--) { 
    var elmt = elements[i]; 
    if (!elmt._wasDisabled) { 
      elmt.disabled = false; 
    } 
    else { 
      elmt._wasDisabled = null; 
    } 
  } 
} 

function enableFormElements() { 
  enableElements(_form.getElementsByTagName("INPUT"));
  enableElements(_form.getElementsByTagName("SELECT"));
  enableElements(_form.getElementsByTagName("TEXTAREA"));
  enableElements(_form.getElementsByTagName("BUTTON"));
  enableElements(_form.getElementsByTagName("A"));
} 

Make sure that the variable _form is set to the ASP.net form on the page. Then just call enableFormElements() and disableFormElements().

 

3. The last way to workaround this problem is to override the Render Event of the page to place the hidden fields for Encrypted Viewstate and Event validation on the top of the form.  This will ensure that these things get written out before anything that can submit the form:

 

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

Another solution based on #3 above, special thanks to Alex for posting this in the comments below.  He wrote a small class called BasePage that fixes the issues, so you just have to extend your page from BasePage instead of Page:

 public class BasePage : Page
 {
   private static string[] aspNetFormElements = new string[] 
   { 
     "__EVENTTARGET",
     "__EVENTARGUMENT",
     "__VIEWSTATE",
     "__EVENTVALIDATION",
     "__VIEWSTATEENCRYPTED",
   };
  
   protected override void Render(HtmlTextWriter writer)
   {
     StringWriter stringWriter = new StringWriter();
     HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);
     base.Render(htmlWriter);
     string html = stringWriter.ToString();
     int formStart = html.IndexOf("<form");
     int endForm = -1;
     if (formStart >= 0)
       endForm = html.IndexOf(">", formStart);
  
     if (endForm >= 0)
     {
       StringBuilder viewStateBuilder = new StringBuilder();
       foreach (string element in aspNetFormElements)
       {
         int startPoint = html.IndexOf("<input type=\"hidden\" name=\"" + element + "\"");
         if (startPoint >= 0 && startPoint > endForm)
         {
           int endPoint = html.IndexOf("/>", startPoint);
           if (endPoint >= 0)
           {
             endPoint += 2;
             string viewStateInput = html.Substring(startPoint, endPoint - startPoint);
             html = html.Remove(startPoint, endPoint - startPoint);
             viewStateBuilder.Append(viewStateInput).Append("\r\n");
           }
         }
       }
  
       if (viewStateBuilder.Length > 0)
       {
         viewStateBuilder.Insert(0, "\r\n");
         html = html.Insert(endForm + 1, viewStateBuilder.ToString());
       }
     }
  
     writer.Write(html);
   }
 }

There are many other workarounds for the above mentioned problem; however no definite solution.

Further Information around this problem

ASP.NET Forum link

Connect Bug

Also, if you use Server.Transfer on your page, you you can also run into this issue.  For information on that, take a look at the documentation found here.  Specifically:

If you set preserveForm to true and if the enableViewStateMac attribute of the pages configuration element is true, ASP.NET will raise an exception when Transfer is executed because the view state from the page that calls Transfer is not valid on the destination page. One of the preserved form fields on the calling page is the hidden __VIEWSTATE form field, which holds the view state for the page. When enableViewStateMac is true, ASP.NET runs a message authentication check on the view state of the destination page when the page is posted back from the client and the check will fail.

And there is a KB article on this with a few resolutions.

kick it on DotNetKicks.com