Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I prevent multiple form submission in .NET MVC without using Javascript?

Tags:

asp.net-mvc

I want to prevent users submitting forms multiple times in .NET MVC. I've tried several methods using Javascript but have had difficulties getting it to work in all browsers. So, how can I prevent this in my controller? It there some way that multiple submissions can be detected?

like image 380
Phil Hale Avatar asked Nov 22 '10 21:11

Phil Hale


People also ask

How can we prevent resubmission of a form in asp net MVC?

After Form submission, when the Refresh Button in the Browser is clicked or the F5 key is pressed, a warning popup comes up which warns against Form resubmission. The solution is very simple, either the page must be redirected to itself or to some other page in order to avoid this particular behavior.


2 Answers

Updated answer for ASP.NET Core MVC (.NET Core & .NET 5.0)

Update note: Remember ASP.NET Core is still called "Core" in .NET 5.0.

I'm going to stick to the least-impact use case like before, where you're only adorning those controller actions that you specifically want to prevent duplicate requests on. If you want to have this filter run on every request, or want to use async, there are other options. See this article for more details.

The new form tag helper now automatically includes the AntiForgeryToken so you no longer need to manually add that to your view.

Create a new ActionFilterAttribute like this example. You can do many additional things with this, for example including a time delay check to make sure that even if the user presents two different tokens, they aren't submitting multiple times per minute.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class PreventDuplicateRequestAttribute : ActionFilterAttribute {     public override void OnActionExecuting(ActionExecutingContext context) {         if (context.HttpContext.Request.HasFormContentType && context.HttpContext.Request.Form.ContainsKey("__RequestVerificationToken")) {             var currentToken = context.HttpContext.Request.Form["__RequestVerificationToken"].ToString();             var lastToken = context.HttpContext.Session.GetString("LastProcessedToken");              if (lastToken == currentToken) {                 context.ModelState.AddModelError(string.Empty, "Looks like you accidentally submitted the same form twice.");             }             else {                 context.HttpContext.Session.SetString("LastProcessedToken", currentToken);             }         }     } } 

By request, I also wrote an asynchronous version which can be found here.

Here's a contrived usage example of the custom PreventDuplicateRequest attribute.

[HttpPost] [ValidateAntiForgeryToken] [PreventDuplicateRequest] public IActionResult Create(InputModel input) {     if (ModelState.IsValid) {         // ... do something with input          return RedirectToAction(nameof(SomeAction));     }      // ... repopulate bad input model data into a fresh viewmodel      return View(viewModel); } 

A note on testing: simply hitting back in a browser does not use the same AntiForgeryToken. On faster computers where you can't physically double click the button twice, you'll need to use a tool like Fiddler to replay your request with the same token multiple times.

A note on setup: Core MVC does not have sessions enabled by default. You'll need to add the Microsoft.AspNet.Session package to your project, and configure your Startup.cs properly. Please read this article for more details.

Short version of Session setup is: In Startup.ConfigureServices() you need to add:

services.AddDistributedMemoryCache(); services.AddSession(); 

In Startup.Configure() you need to add (before app.UseMvc() !!):

app.UseSession(); 

Original answer for ASP.NET MVC (.NET Framework 4.x)

First, make sure you're using the AntiForgeryToken on your form.

Then you can make a custom ActionFilter:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class PreventDuplicateRequestAttribute : ActionFilterAttribute {     public override void OnActionExecuting(ActionExecutingContext filterContext) {         if (HttpContext.Current.Request["__RequestVerificationToken"] == null)             return;          var currentToken = HttpContext.Current.Request["__RequestVerificationToken"].ToString();          if (HttpContext.Current.Session["LastProcessedToken"] == null) {             HttpContext.Current.Session["LastProcessedToken"] = currentToken;             return;         }          lock (HttpContext.Current.Session["LastProcessedToken"]) {             var lastToken = HttpContext.Current.Session["LastProcessedToken"].ToString();              if (lastToken == currentToken) {                 filterContext.Controller.ViewData.ModelState.AddModelError("", "Looks like you accidentally tried to double post.");                 return;             }              HttpContext.Current.Session["LastProcessedToken"] = currentToken;         }     } } 

And on your controller action you just...

[HttpPost] [ValidateAntiForgeryToken] [PreventDuplicateRequest] public ActionResult CreatePost(InputModel input) {    ... } 

You'll notice this doesn't prevent the request altogether. Instead it returns an error in the modelstate, so when your action checks if ModelState.IsValid then it will see that it is not, and will return with your normal error handling.

like image 78
Jim Yarbro Avatar answered Sep 21 '22 09:09

Jim Yarbro


I've tried several methods using Javascript but have had difficulties getting it to work in all browsers

Have you tried using jquery?

$('#myform').submit(function() {     $(this).find(':submit').attr('disabled', 'disabled'); }); 

This should take care of the browser differences.

like image 29
Darin Dimitrov Avatar answered Sep 21 '22 09:09

Darin Dimitrov