views:

367

answers:

4

During a log inspection of my ASP.NET MVC application I discovered that some users managed to post same form twice. I tried to reproduce it but I failed. Anyway I'd like to prevent users from posting same data twice. Is there any canonical solution for that in ASP.NET MVC or I have to develop my own custom solution?

I case of custom solution I can imagine client side and server side solution. Can you recommend me anything in either case?

+7  A: 

The usual approach is to disable the submit button immediately after clicking it.

Something like this with jQuery should work:

<script type="text/javascript">
    $("#myform").submit(function()
        { $("#submit-button").attr("disabled", "disabled"); });
</script>


Now after reading closely your question and seeing that you use ASP.NET MVC....

The double post is probably caused because users refresh the page after posting it. If you simply return a view from your post action:

[AcceptVerbs (HttpVerbs.Pos)]
ActionResult ProcessUserPost ()
{
    /*...*/

    return View ();
}

Then the browser will attempt to recreate the page by performing the same steps, meaning submitting the remembered form again. Some browsers will display a warning message (FireFox), the others will just do it quietly (looking at Opera). In order to avoid this you need to assure the page the user is getting is the result of a GET request not POST.

Basically, you need to perform a redirect instead of returning a view. This will instruct the browser to fetch a page again.

[AcceptVerbs (HttpVerbs.Pos)]
ActionResult ProcessUserPost ()
{
    /*...*/

    return RedirectToAction ("DisplayTheForm");
}

This way refreshing the page won't cause a new POST request being sent.

Of course, this will not prevent your users clicking the back button to return to the form page to submit it again. But that situation should be handled in your business logic, check for double submission of the same order by the same user of whatever it is that you are doing.

Developer Art
I'll like this solution however this doesn't apply in some weird back button scenarios. (BTW: in my particular case I cannot reproduce double posting even with back button)
Jakub Šturc
Have you compared META header tags related to caching on your production and your test site? They may differ. And they may be set to refresh data on Back button.
Robert Koritnik
@Robert: I tested this at production environment. I am suspicious that only IE6 and IE7 are capable of doing this. Anyway how it's happen is only important to avoid it in future.
Jakub Šturc
+3  A: 

I can think of three ways to handle this.

  1. Have a double post be non-destructive. That is, when possible make it so that a repost simply refreshes the data in a way that renders it still valid. This isn't really possible with a post that creates something -- say a credit card transaction -- but should suffice for an update method when you are basically setting new properties.
  2. Prevent double posts using a one-time nonce that is only good for one action. Include a hidden nonce on the page that you track and mark as used once the first post has been seen. Check this nonce when you get it to make sure that it hasn't been used before and, if it has, show an error message rather than taking an action.
  3. Prevent destructive actions by checking uniqueness constraints on the posted data. This is really a variant on (2) in that your nonce is basically some unique value in the actual data. This works for some creation scenarios, i.e., you can't create a duplicate user with the same email address, but fails when their is no unique field in the data. An example of the latter might be a purchase transaction where the user might buy the same item more than one time.
tvanfosson
#2 is a great suggestion (+1 from me). I'd save this value with newly created record.
Robert Koritnik
@Robert: overriding the first post ins't always option but surely is something that should be considered
Jakub Šturc
@Jakub: Your comment was probably intended at @tvanfosson not me... ;)
Robert Koritnik
@Robert: No. You was suggested that at #2 you would save the values from the second post. I think that in many cases it's what user expected to happen however in some cases this isn't option.
Jakub Šturc
+2  A: 

One technique I have used to prevent a double post is using a server side and client side key that changes with each post. It works like this:

  1. Generate a unique (random) key on the server and place it in the session and also in a hidden field.

  2. When the user posts back the first time compare the key in the hidden field to the key in the session, and, if they match, accept the input and then change or remove the key from the session and update the hidden field as well.

  3. If the user manages to click submit twice the second post will fail because the hidden field in the HTML will no longer match the session variable until the page has been refreshed.

Nathan Taylor
+2  A: 

Two step process

Disable buttons first.
This will prevent users from submitting the form twice while browser waits for server response. But you should be careful with this one. When user hits submit, you will have to first validate your form (if you use client validation) and when it's valid, imediatelly disable buttons using this code.

$(":submit, :button").attr("disabled", true);

Use one of the following redirect action results in your controller
This will prevent users from hitting F5 after the form has been processes to resend POST data. This is called POST-REDIRECT-GET pattern.

RedirectToAction();
RedirectToRoute();
Redirect(); // redirects to URL


EDIT
As you've said in comments, your page already uses P-R-G pattern. In this case I'd make sure to implement first step.

Robert Koritnik
BTW althought I am using Post-Redirect-Get pattern in many cases I always feel bad about additional request which hurts performance.
Jakub Šturc
I don't think you can avoid excessive requests caused by F5 or reloads or whatever other reason users are doing.
Robert Koritnik