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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4250604/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 00:35:59  来源:igfitidea点击:

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

asp.net-mvc

提问by Phil Hale

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?

我想防止用户在 .NET MVC 中多次提交表单。我已经尝试了几种使用 Javascript 的方法,但很难让它在所有浏览器中工作。那么,如何在我的控制器中防止这种情况发生?有什么方法可以检测到多个提交?

采纳答案by Lorenzo

Dont reinvent the wheel :)

不要重新发明轮子:)

Use the Post/Redirect/Getdesign pattern.

使用Post/Redirect/Get设计模式。

Here you can find a questionand an answer giving some suggestions on how to implement it in ASP.NET MVC.

在这里,您可以找到一个问题和一个答案,提供有关如何在 ASP.NET MVC 中实现它的一些建议。

回答by Jim Yarbro

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

首先,确保您在表单上使用 AntiForgeryToken。

Then you can make a custom ActionFilter:

然后你可以制作一个自定义的 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.IsValidthen it will see that it is not, and will return with your normal error handling.

您会注意到这并不能完全阻止请求。相反,它在模型状态中返回一个错误,因此当您的操作检查是否ModelState.IsValid然后它会看到它不是,并将返回您的正常错误处理。



UPDATE: Here's an ASP.NET Core MVC solution

更新:这是一个 ASP.NET Core MVC 解决方案

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 articlefor more details.

我将像以前一样坚持影响最小的用例,您只装饰那些您特别想要防止重复请求的控制器操作。如果您想在每个请求上运行此过滤器,或者想使用异步,还有其他选项。有关更多详细信息,请参阅此文章

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

新的表单标签助手现在会自动包含 AntiForgeryToken,因此您不再需要手动将其添加到您的视图中。

Create a new ActionFilterAttributelike 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.

创建一个新的ActionFilterAttribute像这个例子。你可以用它做很多额外的事情,例如包括时间延迟检查,以确保即使用户提供两个不同的令牌,他们也不会每分钟提交多次。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class PreventDuplicateRequestAttribute : ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext context) {
        if (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 PreventDuplicateRequestattribute.

这是自定义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);
}

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 Fiddlerto replay your request with the same token multiple times.

关于测试的说明:简单地在浏览器中回击不会使用相同的 AntiForgeryToken。在无法物理双击按钮的速度更快的计算机上,您需要使用Fiddler 之类的工具来多次使用相同的令牌重放您的请求。

A note on setup: Core MVC does not have sessions enabled by default. You'll need to add the Microsoft.AspNet.Sessionpackage to your project, and configure your Startup.csproperly. Please read this articlefor more details.

设置注意事项:Core MVC 默认没有启用会话。您需要将Microsoft.AspNet.Session包添加到您的项目中,并Startup.cs正确配置您的项目。请阅读这篇文章了解更多详情。

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

会话设置的简短版本是: 在Startup.ConfigureServices()您需要添加:

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

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

Startup.Configure()你需要添加(之前app.UseMvc()!!):

app.UseSession();

回答by Darin Dimitrov

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

我已经尝试了几种使用 Javascript 的方法,但很难让它在所有浏览器中工作

Have you tried using jquery?

你试过使用jquery吗?

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

This should take care of the browser differences.

这应该注意浏览器的差异。

回答by VinnyG

Just to complete the answer of @Darin, if you want to handle the client validation (if the form has required fields), you can check if there's input validation error before disabling the submit button :

只是为了完成@Darin 的回答,如果您想处理客户端验证(如果表单有必填字段),您可以在禁用提交按钮之前检查是否存在输入验证错误:

$('#myform').submit(function () {
    if ($(this).find('.input-validation-error').length == 0) {
        $(this).find(':submit').attr('disabled', 'disabled');
    }
});

回答by aamir sajjad

what if we use $(this).valid()

如果我们使用 $(this).valid()

 $('form').submit(function () {
            if ($(this).valid()) {
                $(this).find(':submit').attr('disabled', 'disabled');
            }
        });

回答by aamir sajjad

You could include a hidden (random or counter) value in the form post, a controller could track these values in an 'open' list or something similar; every time your controller hands out a form it embeds a value, which it tracks allowing one post use of it.

您可以在表单帖子中包含一个隐藏的(随机或计数器)值,控制器可以在“打开”列表或类似的东西中跟踪这些值;每次您的控制器分发一个表单时,它都会嵌入一个值,它会跟踪该值以允许一个帖子使用它。

回答by Leo Barbas

Just add this code at the end of your page. I am using "jquery-3.3.1.min.js" and "bootstrap 4.3.1"

只需在页面末尾添加此代码即可。我正在使用“jquery-3.3.1.min.js”和“bootstrap 4.3.1”

<script type="text/javascript">
    $('form').submit(function () {
        if ($(this).valid()) {
            $(this).find(':submit').attr('disabled', 'disabled');
        }
    });
</script>

回答by Nijas Hameed

Use this simple jquery input field and will work awesomely even if you have multipple submit buttons in a single form.

使用这个简单的 jquery 输入字段,即使在一个表单中有多个提交按钮,它也能很好地工作。

$('input[type=submit]').click(function () {
    var clickedBtn = $(this)
    setTimeout(function () {
        clickedBtn.attr('disabled', 'disabled');
    }, 1);
});

回答by Jim Berg

You can do this by creating some sort of static entry flag that is user specific, or specific to whatever way you want to protect the resource. I use a ConcurrentDictionary to track entrance. The key is basically the name of the resource I'm protecting combined with the User ID. The trick is figuring out how to block the request when you know it's currently processing.

您可以通过创建某种特定于用户或特定于您想要保护资源的任何方式的静态条目标志来实现此目的。我使用 ConcurrentDictionary 来跟踪入口。密钥基本上是我保护的资源的名称与用户 ID 的组合。诀窍是弄清楚如何在您知道当前正在处理请求时阻止它。

public async Task<ActionResult> SlowAction()
{
    if(!CanEnterResource(nameof(SlowAction)) return new HttpStatusCodeResult(204);
    try
    {
        // Do slow process
        return new SlowProcessActionResult();
    }
    finally
    {
       ExitedResource(nameof(SlowAction));
    }
}

Returning a 204 is a response to the double-click request that will do nothing on the browser side. When the slow process is done, the browser will receive the correct response for the original request and act accordingly.

返回 204 是对双击请求的响应,不会在浏览器端做任何事情。当缓慢的过程完成后,浏览器将收到原始请求的正确响应并采取相应的行动。

回答by server info

You can also pass some sort of token in a hidden field and validate this in the controller.

您还可以在隐藏字段中传递某种令牌并在控制器中验证它。

Or you work with redirects after submitting values. But this get's difficult if you take heavily advantage of ajax.

或者您在提交值后使用重定向。但是,如果您大量使用 ajax,这将变得很困难。