我想防止用户在 .NET MVC 中多次提交表单。我已经尝试了几种使用 Javascript 的方法,但很难让它在所有浏览器中工作。那么,如何在我的 Controller 中防止这种情况发生呢?有什么方法可以检测到多次提交吗?
请您参考如下方法:
更新了 ASP.NET Core MVC(.NET Core 和 .NET 5.0)的答案
更新说明:记住 ASP.NET Core 是 still called "Core" in .NET 5.0 .
我将像以前一样坚持影响最小的用例,您只装饰那些您特别想要防止重复请求的 Controller 操作。如果您想让这个过滤器在每个请求上运行,或者想使用异步,还有其他选项。请参阅this article了解更多详情。
新的表单标记帮助程序现在自动包含 AntiForgeryToken,因此您不再需要手动将其添加到 View 中。
像本例一样创建一个新的ActionFilterAttribute
。您可以用它做许多其他事情,例如包括时间延迟检查,以确保即使用户提供两个不同的 token ,他们也不会每分钟提交多次。
[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);
}
}
}
}
应要求,我还写了一个异步版本which can be found here .
以下是自定义 PreventDuplicateRequest
属性的人为使用示例。
[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);
}
测试注意事项:仅在浏览器中回击不会使用相同的 AntiForgeryToken。在速度更快的计算机上,您无法双击该按钮两次,您需要使用类似 Fiddler 的工具。使用相同的 token 多次重放您的请求。
设置注意事项:Core MVC 默认情况下不启用 session 。您需要将 Microsoft.AspNet.Session
包添加到您的项目中,并正确配置 Startup.cs
。请阅读this article了解更多详情。
session 设置的简短版本是: 在 Startup.ConfigureServices()
中,您需要添加:
services.AddDistributedMemoryCache();
services.AddSession();
在 Startup.Configure()
中,您需要添加(之前 app.UseMvc()
!!):
app.UseSession();
<小时 />
ASP.NET MVC (.NET Framework 4.x) 的原始答案
首先,确保您在表单上使用 AntiForgeryToken。
然后你可以制作一个自定义的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;
}
}
}
在您的 Controller 操作中,您只需...
[HttpPost]
[ValidateAntiForgeryToken]
[PreventDuplicateRequest]
public ActionResult CreatePost(InputModel input) {
...
}
您会注意到这并不会完全阻止请求。相反,它会在 modelstate 中返回一个错误,因此当您的操作检查 ModelState.IsValid
时,它会发现它不是,并将返回正常的错误处理。