Click to search the site Click to log in
Online articles
Download free tools
Support pages, per product
Services
Frequently asked questions, per product
Preventing double form submit in ASP.Net
Author: George Mihaescu
Published: February 22, 2007
Category: Implementation technique / ASP.Net 2.0 / JavaScript
Notes: Tested with Firefox 1.x, 2.0 and IE 6 and 7
Description: This document presents a number of solutions for preventing the user to click the Submit button in a form multiple times. with special emphasis (and examples in C#) on disabling the submit button while the form is processing. The solution presented is simple, tried and tested, and works with client-side validation and validation groups.
View count: 47,634
Comments: 13 Read comments or post your own

  Print viewOpens in new window
 Preventing double submit in ASP

Preventing double form submit in ASP.NET (2.0)

 

By George Mihaescu

 

Summary: Sometimes pages take long to process on the server side and it is critical that the user does not submit the form multiple times by repeatedly clicking the submit button while waiting for the form to process. This document presents a number of solutions for this, with special emphasis (and examples in C#) on disabling the submit button while the form is processing.

 

The solutions were tested on .Net 2.0 across IE (6 & 7) and Firefox (1.5 and 2) in fairly complex usage scenarios (master pages, user controls, multiple groups of controls in different validation groups, etc). They were not tested on .Net 1.x (they probably will not work). The best solution I have found so far is the last one listed (disable the form’s submit button while the form is being processed).

The problem

Some forms may take long to process on the server side; typical examples are forms that interact with 3rd part systems, such as on-line order processing systems (e.g. credit card payment forms) or email systems (e.g. user registration forms, where the user needs to be emailed his registration info). There is nothing you can do to control the duration of the form processing in such instances, and you don’t want the user to re-submit the form by clicking the submit button again without realizing that the form is already submitted (or just being frustrated by the wait).

Obviously many other forms may exhibit the same problem, but depending on the specifics of your application, it may not be critical that the form is not re-submitted. It’s up to you to decide where you want to apply the solutions listed below (and which solution, depending on your circumstances).

The solutions

I found a number of solutions to this problem; I will list them all, for completeness, although I will be focusing on the ones that deal with disabling the submit button in the form as I find those to be the best in terms of usability (user feedback) and complexity of implementation.

1.              Server-side solution: issue a token

Simply stated, this solution consists of creating a token (such as DateTime.Now.Ticks) and placing that into a session variable and into a hidden field of the form when first presenting the form to the user. On the server, when the form is submitted, check the value of the hidden field and compare it with the one stored in the session; if they are the same, this is the first submit, so remove the token from the session and start processing the form. Subsequent submissions will not find the token in the session anymore, and at that point you can decide how to handle this: simply ignore the submission, or show the user a page saying “your form is already being processed”, etc.

 

The disadvantage of this is that the user has no feedback in the browser that his form is actually being processed upon his first click (well, he actually does because browsers do show a progress bar, an animated icon of some sort, or similar, but those may be too subtle sometimes).

2.              Server-side solution: asynchronous processing

What I'm referring to here is not the standard asynchronous ASP.Net page processing – this will not help in our problem as the request does not return until the processing has completed (just like in normal, synchronous processing) and therefore the user will still be able to click the submit button again.

What I am referring to is a scenario in which in the server-side click handler of your submit button start the asynchronous form processing (i.e. on a worker thread) and return the user a page with the message “Your form is being processed, please wait” (through a Response.Redirect () or Server.Transfer (), etc) – this page will have to periodically poll the server to determine the result of the asynchronous processing.

 

This solution is not the best in terms of performance and reliability (due to the need to poll the server), and adds substantial complexity to the whole process. In addition, there is also the usability issue, which may be critical here: the user can actually navigate to a different page after seeing this message because he feels that the submission was ok (as opposed to still seeing the original form, where he would feel compelled to wait for a message). So consider the client-side solutions below and you’ll see that there’s no need to delve into those complexities.

 

3.              Client-side solution: change the form submission code (limited)

I’ll start by stating that this solution (unlike the one below) is limited because it will not work if you page contains input controls that belong to different validation groups. For instance, you may have a side-bar that contains a search text box + its button, and the login controls (user text box, password text box + the login button), plus you actual page controls and their submit button. As you want each group of such functionally related controls to work independently (as if they were in different forms) you will place them in different validation groups. In this scenario, the solution below will not work, as it will always validate all the controls on the page, regardless of their validation group.

 

The solution consists in adding code in your Page_Load to modify the client-side java script called when the form is submitted. The default code generated by ASP.Net (2.0) for this is:

 

<script type="text/javascript">

<!--

function WebForm_OnSubmit()

{

if (typeof(ValidatorOnSubmit) == "function" && ValidatorOnSubmit() == false)

       return false;

return true;

}

// -->

</script>

 

Now, if you could insert somehow some javascript code just above returning true (i.e. after form validation was passed ok), to disable the submit button, the problem is solved. The answer is that you cannot insert javascript right before the return true statement, but you can right at the beginning of the function, which is good enough, because you can always return true (and render the existing code dead). To do this, use the ClientManager.RegisterOnSubmitStatement method, as shown below (note that I have not used the StringBuilder, I just concatenated on a String, which is not a good idea):

 

protected void Page_Load(object sender, EventArgs e)

{

//SOLUTION 1: CHANGE THE FORM SUBMIT CODE TO VALIDATE THE PAGE, THEN DISABLE //THE BUTTON BEFORE SUBMITTING

//DOES NOT WORK IF PAGE CONTAINS MULTIPLE CONTROLS IN DIFFERENT VALIDATION //GROUPS BECAUSE IT VALIDATES ALL CONTROLS

    String jsname = "OnSubmitScript";

    Type jstype = this.GetType();

 

    // Check to see if the OnSubmit statement is already registered.

    if (!Page.ClientScript.IsOnSubmitStatementRegistered(jstype, jsname))

    {

        String jstext = "if (typeof(ValidatorOnSubmit) == 'function' && ValidatorOnSubmit() == false)return false;\n";

        jstext += "else\n";

        jstext += "{\n";

        jstext += "\t var button = document.getElementById('" + this.buttonSubmit.ClientID + "');\n";

        jstext += "\t button.value = 'Processing...';\n";

        jstext += "\t button.disabled = true;\n";

        jstext += "}\n";

        jstext += "return true;\n";

        Page.ClientScript.RegisterOnSubmitStatement(jstype, jsname, jstext);

    }

}

 

Note that you have to execute this code on all Page_Load calls (i.e. don’t do it only if (!IsPostback)) because you may have server-side validation code that will re-render the page on an invalid submission. This will produce the following javascript in your page (I colored the new code in green):

 

<script type="text/javascript">

<!--

function WebForm_OnSubmit()

{

if (typeof(ValidatorOnSubmit) == 'function' && ValidatorOnSubmit() == false)return false;

else

{

        var button = document.getElementById('ctl00_ContentPlaceHolder1_buttonSubmit');

        button.value = 'Processing...';

        button.disabled = true;

}

return true;

if (typeof(ValidatorOnSubmit) == "function" && ValidatorOnSubmit() == false) return false;

return true;

}

 

Essentially this renders the existing code dead (by doing a return true just above it) and replaces it with code that first does the same validation (using ValidatorOnSubmit) and if it finds the form to be valid, changes the text on the submit button to “Processing…” then disables it.

Note that in the C# code (in Page_Load) I used buttonSubmit.ClientID to get the client-side ID of the button, as the button may be in a user-control, in a child page of a master page, etc. and its ID will be generated dynamically by ASP.Net – so don’t hard-code it).

 

The problem with this solution is that ValidatorOnSubmit (at least as called above) will validate ALL controls on the page, regardless of their validation group. So if you do have controls in different validation groups you will find that your form is not submitted because it does not pass client-side validation. Maybe a solution exists to this issue, but I did not dig deep as I have below a solution that works in all contexts I’ve tried.

 

4.              Client-side solution: change the button submission code (best and recommended)

This is the best solution I have found so far. It consists of modifying the client-side javascript of the submit button to perform client-side validation only on the validation group the button belongs to, and if validation is passed, to submit the form.

 

The standard code generated by ASP.Net 2.0 for a submit button is:

 

onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions('buttonSubmit', '', true, '';, '';, false, false))"

 

Now, we could think of doing something similar to the above solution: insert javascript code before the call to WebForm_DoPostBackWithOptions to validate the form, and if found valid, disable the submit button and continue with calling  WebForm_DoPostBackWithOptions. However, there is a small issue with this – the paragraph below explains it:

 

WebForm_DoPostBackWithOptions is new to ASP.Net 2.0. It was added to replace the _doPostback function of 1.x and provide better functionality (client-side validation preserving scroll position, etc) before submitting the form. However, one new behavior is that it will not submit the form if the control (e.g. button) that called it is not visible and enabled). What this means for us is that we cannot simply disable the submit button before calling WebForm_DoPostBackWithOptions on it, as the call will not submit because we’ve disabled the button.

 

So we’ll do this: add javascript code right at the beginning of the onclick handler to validate the form controls in the same validation group with the submit button. If the controls are valid, then disable the button and post the form using the old __doPostback call (so that we force posting the form, circumventing the checks in the new WebForm_DoPostBackWithOptions).

 

You will need to add the following code to your Page_Load:

 

protected void Page_Load(object sender, EventArgs e)

{

    if (!IsPostBack)

    {

        //SOLUTION 2: CHANGE THE BUTTON SUBMIT JS CODE SO THAT IT

        //DISABLES WHEN THE FORM IS VALID, PRIOR TO SUBMISSION

 

        //build the JS

        StringBuilder sb = new StringBuilder();

 

        sb.Append("if (typeof(Page_ClientValidate) == 'function') { ");

 

        //if client-side does not validate, stop (this supports validation groups)

        //BUGFIX: must save, then restore the page validation / submission state, otherwise

        //when the validation fails, it prevents the FIRST autopostback from other controls

        sb.Append("var oldPage_IsValid = Page_IsValid; var oldPage_BlockSubmit = Page_BlockSubmit;");

        sb.Append("if (Page_ClientValidate('" + buttonSubmit.ValidationGroup + "') == false) {");

        sb.Append(" Page_IsValid = oldPage_IsValid; Page_BlockSubmit = oldPage_BlockSubmit; return false; }} ");

 

        //change button text and disable it

        sb.Append("this.value = 'Processing...';");

        sb.Append("this.disabled = true;");

 

        //insert the call to the framework JS code that does the postback of the form in the client

        //The default code generated by ASP (WebForm_DoPostbackWithOptions) will not

        //submit because the button is disabled (this is new in 2.0)

        sb.Append(ClientScript.GetPostBackEventReference(buttonSubmit, null) + ";");

 

        //BUGFIX: MUST RETURN AFTER THIS, OTHERWISE IF THE BUTTON HAS UseSubmitBehavior=false

        //THEN ONE CLICK WILL IN FACT CAUSE 2 SUBMITS, DEFEATING THE WHOLE PURPOSE

        sb.Append("return true;");

 

        string submit_button_onclick_js = sb.ToString();

 

        buttonSubmit.Attributes.Add("onclick", submit_button_onclick_js);

    }

}

 

This actually inserts calls to Page_ClientValidate client-side javascript (ASP.Net function) which can take a validation group name as a parameter. Note that I’ve used the button’s ValidationGroup property to insert the name of the validation group as a parameter to this function. Then, if validation is passed on that validation group, the button label is changed, the button is disabled and __doPostback is called to do the posting of the form (the name of the __doPostback function is obtained using the ClientManager.GetPostBackEventReference method).

 

Note that you can do the above on if (!IsPostback) in your Page_Load, unlike the solution before. The result is the following onclick javascript code inserted for the submit button:

 

onclick="if (typeof(Page_ClientValidate) == 'function') { var oldPage_IsValid = Page_IsValid; var oldPage_BlockSubmit = Page_BlockSubmit;if (Page_ClientValidate('') == false) { Page_IsValid = oldPage_IsValid; Page_BlockSubmit = oldPage_BlockSubmit; return false; }} this.value = 'Processing...';this.disabled = true;__doPostBack('buttonSubmit','');return true;WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions('buttonSubmit', '', true, '', '', false, true))"

 

In my case, the button was in the default (un-named) validation group and that’s why Page_ClientValidate is called with an empty string parameter. The call to WebForm_DoPostbackWithOption remains (we cannot remove it) but will do nothing as we don't reach it due to the return statement we insert just above it.

 

This solution has worked when used in a child page of a master page, or in a user control, with multiple logical control groups belonging to different validation groups. This seems to be the best solution because it is low complexity, robust and has the best usability: the user is kept on the same page (therefore he’s expecting a result and does not feel free to navigate to another page) but he’s unable to perform multiple submissions. This was tested on IE 6 & 7 and Firefox 1.5 and 2.0 and worked flawlessly.

 


Reader comments:
Name: (optional)
Verification text:    
(type as in image next to it)
Comment: max 2,000 characters; for security reasons no active content / no HTML formatting is supported.
Please stick to the subject of the article; comments are reviewed and unrelated / inappropriate ones will be deleted.

On Aug 30, 2010 at 3:07 EST thank's said:

thank's

On Aug 24, 2010 at 10:21 EST Uri L. said:

GREAT POST! Thank YOU!!! finally found the solution to working with client side JS validation in conjunction with ASP.NET field validation!

On Jun 27, 2010 at 10:39 EST c said:

vbvbv

On Sep 23, 2009 at 6:12 EST Shashikumar said:

I have a login page as user control. When clicking on the submit button the form is not submitting in fire fox (i.e, the login page postbacks but stays in login page itself) but works well in all IE. I have deployed this control in umbraco. Any suggestions will be helpful. thanks.

On Aug 29, 2008 at 16:41 EST George said:

Not sure what you mean, Connie. Where do you want a different page? Please describe your scenario and what you're trying to achieve and maybe I can help.

On Aug 28, 2008 at 14:37 EST Connie said:

Excellent. But what if I do want a different page? What event can be use to make the step?

On Jun 21, 2007 at 1:22 EST George said:

Marcus, I updated the code above fixing the problem you've described (see the first line commented BUGFIX). In addition I actually discovered another issue with your sample: for whatever reason you have set the UseSubmitBehavior="False" on the button that submits the form, something I have never tested so far (the default is true). In your sample, once click on the button causes 2 submits (not only defeating the whole purpose of the article, but actually forcing a double submit with any single click! talk about ironic...), I fixed that issue too (see the second line commented BUGFIX). The problem was that the old code did not eliminate the standard call to WebForm_DoPostBackWithOptions, which did not fire when UseSubmitBehavior="True", but does when UseSubmitBehavior="False". I added the return statement to make sure that never gets executed again. Please let me know how this works for you.

On Jun 20, 2007 at 16:54 EST George said:

Marcus, thanks for the code, I only got to work on it now; yes, I could see the problem and will try to understand what happens and what I can do to fix that behaviour. Again, thanks for pointing this out to me...

On Jun 20, 2007 at 8:28 EST Marcus said:

Hi George, no problem I'll keep checking back; here is the code for the aspx: <asp:DropDownList ID="ddl" runat="server" AutoPostBack="true" OnSelectedIndexChanged="ddl_OnSelectedIndexChanged" CausesValidation="false"> <asp:ListItem Text="Select..." Value="0"></asp:ListItem> <asp:ListItem Text="Name1" Value="1"></asp:ListItem> <asp:ListItem Text="Name2" Value="2"></asp:ListItem> </asp:DropDownList> <p><asp:Label ID="lbl" runat="server"></asp:Label></p> <br /> <asp:TextBox ID="tb" runat="server"></asp:TextBox> <asp:RequiredFieldValidator ID="tbRequired" runat="server" ControlToValidate="tb" Display="Dynamic" ErrorMessage="Required">*</asp:RequiredFieldValidator> <br /><br /> <asp:Button ID="btn" runat="server" Text="Save" OnClick="btn_OnClick" UseSubmitBehavior="False" /> The drop down sets the label to the selected value, the button executes Thread.Sleep(1000); & the page load executes your code above.

On Jun 20, 2007 at 7:10 EST George said:

I'll try to reproduce the problem today and post back a comment... I wish you've left an email address just in case I'm unable to repro what you've described.

On Jun 20, 2007 at 4:57 EST Anonymous said:

Thanks George, this is really useful however it apparently disables the first autopostback of any other control on the page (for example a drop down list) if client validation errors are received on another control on the page (for example a textbox), when the button is pressed.

On Mar 13, 2007 at 19:12 EST George said:

Yes, Mathew, you're right; calling Page_ClientValidate () with no parameters validates the whole thing, which is not what we want. I think I've seen the same code you are talking about, because that's how I got started on this topic. That did not work for me (it would work though if you don't have multiple controls that logically belong to different forms) so I looked for a solution of my own.

On Mar 11, 2007 at 16:50 EST mathew said:

Excellent, thanks for the solution; I found similar code on Google but without the ability to work when the button is part of a validation group. I guess the key is the line if (Page_ClientValidate('" + buttonSubmit.ValidationGroup + "') == false Other code that I've seen simply calls Page_ClientValidate () i.e. no parameters, which will not do the client-side validation of the group, but of the entire page, so the submission would be blocked by other controls on the page that have nothing to do with the button that does the submission I care about. Am i right?
Copyright 2308 registered users, 24 users online now