Skip to main content
 首页 » 编程设计

asp.net-mvc之如何在不使用 Javascript 的情况下防止 .NET MVC 中的多个表单提交

2024年04月18日6arxive

我想防止用户在 .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 时,它会发现它不是,并将返回正常的错误处理。