Thursday, February 23, 2006 - Posts

Prevent Postback Events from Firing on Page Refresh

One annoying behavior of ASP.NET pages is a replay of postback events when user hit refresh (F5) in browser.
This behavior is easily reproducible:
Place ASP button on Web Form
1. Add some code to Button_Click event
2. View page in browser
3. Check that your code being executed when you hit button
4. Refresh page and observe that your code being executed once again

Explanation is also simple. As far as the browser goes, when user hits refresh, exact previous request sent to server. This way server has no clue if it is postback event, first page hit or refresh.

The only workaround I found based on fact that client side onsubmit event is not fired on refresh. This being said, you can place 'stamp' counter into session and hidden field on page, increment it in submit event and compare to session version back at server.

Complete solution skeleton will look as follows:
ASPX:
<form id="Form1" onsubmit="var o=getElementById('__REFRESHSTAMP');var i=Number(o.value);i++;o.value=i;" 
       method="post" runat="server">
 <asp:button id="Button2" Text="Button" runat="server"></asp:button>
 <input id="__REFRESHSTAMP" type="hidden" value="1" runat="server">
 <asp:label id="Label1" runat="server">False</asp:label>
</form>

Codebehind (Designer generated code skipped):
public class WebForm1 : System.Web.UI.Page
{
   protected System.Web.UI.WebControls.Button Button1;
 
   protected System.Web.UI.HtmlControls.HtmlInputHidden __REFRESHSTAMP;
   private bool m_RefreshStamped;
   protected System.Web.UI.WebControls.Label Label1;
   private bool m_IsRefresh;
   private bool IsRefresh
   {
      get
      {
         if (!this.m_RefreshStamped)
            this.m_IsRefresh = checkRefresh();
         return this.m_IsRefresh;
      }
   }
   private int getValue(string val)
   {
      if (val == null || val.Length == 0)
         return 0;
      return Int32.Parse(val, CultureInfo.InvariantCulture);
   }
   private bool checkRefresh()
   {
      this.m_RefreshStamped = true;
      string stampKey = String.Concat(this.ToString(), "__REFRESHSTAMP");
      int prevStamp = getValue(this.Session[stampKey] as string);
      int stamp = getValue(this.__REFRESHSTAMP.Value);
      if (stamp > prevStamp) // Postback
      {
         this.Session[stampKey] = stamp.ToString(CultureInfo.InvariantCulture);
         return false;
      }
      return true;
   }
   private void Button1_Click(object sender, System.EventArgs e)
   {
      this.Label1.Text = this.IsRefresh.ToString();
      if (!this.IsRefresh)
         this.Button1.Text = "Clicked at: " + DateTime.Now.ToLongTimeString() ;
   }
}

Notes:
1. You should implement this for each page you want to process.
2. You should reset session stamp before redirecting from page.
3. I would suggest constructing complete solution as custom control with IsRefresh property and Clear method.