Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/337.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 防止用户两次提交表单的好方法是什么?_C#_Asp.net Mvc_Form Submit - Fatal编程技术网

C# 防止用户两次提交表单的好方法是什么?

C# 防止用户两次提交表单的好方法是什么?,c#,asp.net-mvc,form-submit,C#,Asp.net Mvc,Form Submit,我有一个购买页面,我不希望用户在进入“订单完成”页面后能够刷新页面并重新提交表单,因为它会通过数据库值在我们的系统中自动设置它们,并通过paypal向它们的卡收费(只希望发生一次)。。。我见过一些网站说“不要点击刷新,否则你会被收取两次费用!”但这是相当跛脚,让它开放的可能性,什么是一个好办法,只允许它提交一次或防止他们刷新,等等 PS:我看到了一些类似的问题,但没有找到令人满意的答案。。。如果有这样的机制,ASP.NETMVC特定的答案也是理想的 编辑:一旦他们点击submit,它就会发布到我

我有一个购买页面,我不希望用户在进入“订单完成”页面后能够刷新页面并重新提交表单,因为它会通过数据库值在我们的系统中自动设置它们,并通过paypal向它们的卡收费(只希望发生一次)。。。我见过一些网站说“不要点击刷新,否则你会被收取两次费用!”但这是相当跛脚,让它开放的可能性,什么是一个好办法,只允许它提交一次或防止他们刷新,等等

PS:我看到了一些类似的问题,但没有找到令人满意的答案。。。如果有这样的机制,ASP.NETMVC特定的答案也是理想的


编辑:一旦他们点击submit,它就会发布到我的控制器,然后控制器会做一些魔术,然后返回一个带有订单完成消息的视图,但是,如果我在浏览器上单击“刷新”,它会执行整个“是否要重新发送此表单?”操作,这很糟糕…

在提供订单确认页面时,您可以设置一个令牌,并将其存储在DB/缓存中。在订单确认的第一个实例中,检查此令牌是否存在并清除该令牌。如果使用线程安全实现,您将无法提交两次订单


这只是众多可能的方法之一

首次加载页面时,为每个访问者的表单提供一个唯一的ID。提交表单时请注意ID。一旦使用该ID提交表单,就不允许使用该ID的任何进一步请求。如果他们单击“刷新”,将发送相同的ID。

在我的头顶上,在页面的GET请求的隐藏字段中生成一个
System.Guid
,并将其与结帐/付款关联。只需检查它,并显示一条消息,说明“已处理付款”或诸如此类。

这方面的标准解决方案是模式。这种模式几乎可以使用任何web开发平台来实现。您通常会:

  • 发布后验证提交
  • 如果失败,则重新呈现原始输入表单,并显示验证错误
  • 如果成功,则重定向到确认页面,或重新显示输入的页面-这是获取部分
  • 由于上一个操作是GET,因此如果用户此时刷新,则不会发生表单重新提交

只需从处理所有讨厌事情的页面重定向到“感谢您的订单”页面。这样,用户就可以随心所欲地多次刷新。

卡齐·曼祖尔·拉希德(Kazi Manzur Rashid)写道(以及其他asp.net mvc最佳实践)。他建议使用两个过滤器来处理POST和下面使用TempData的GET之间的数据传输。

我100%同意RedFilter的一般回答,但想专门发布一些ASP.NET MVC的相关代码

您可以使用解决双重回发问题

以下是问题的图形说明:

发生的情况是,当用户点击“刷新”时,浏览器会尝试重新提交它发出的最后一个请求。如果最后一个请求是帖子,浏览器将尝试这样做

大多数浏览器都知道这通常不是用户想要做的,因此会自动询问:

铬合金- 您正在查找输入的已用信息的页面。 返回到该页面可能会导致重复您采取的任何操作。 要继续吗?
Firefox-要显示此页面,Firefox必须发送重复先前执行的任何操作(如搜索或订单确认)的信息。
狩猎之旅- 您确定要再次发送表单吗? 要重新打开此页面,Safari必须重新发送表单。这可能会导致重复购买、评论或其他操作。
Internet Explorer- 要再次显示网页,web浏览器需要 重新发送您以前提交的信息。 如果您正在进行购买,则应单击“取消”以 避免重复交易。否则,单击“重试”以显示 再次打开网页

但是PRG模式通过向客户端发送重定向消息来帮助避免这种情况,因此当页面最终出现时,浏览器执行的最后一个请求是对新资源的GET请求

下面是一个示例,它提供了MVC模式的实现。需要注意的是,您只希望在服务器上执行非操作时使用重定向。换句话说,如果您有一个有效的模型,并且确实以某种方式持久化了数据,那么确保请求不会意外提交两次是很重要的。但是如果模型无效,则应返回当前页面和模型,以便用户可以进行任何必要的修改

下面是一个示例控制器:

[HttpGet]
公共操作结果编辑(int id){
var模型=新的EditModel();
//...
返回视图(模型);
}
[HttpPost]
公共操作结果编辑(编辑模型){
if(ModelState.IsValid){
product=repository.SaveOrUpdate(模型);
返回RedirectToAction(“Details”,new{id=product.id});
}
返回视图(模型);
}
[HttpGet]
公共行动结果详细信息(int id){
var model=新的DetailModel();
//...
返回视图(模型);
}

请注意,PRG模式并不能完全防止多个表单提交,因为即使在单个重定向发生之前,也可能会触发多个post请求-这可能会导致表单提交失败

请注意已提供的答案,该答案为解决此问题提供了一个解决方案,为了方便起见,我在此引用该答案:

如果您在表单中使用隐藏的防伪令牌(如您所愿) 应该),您可以在首次提交时缓存防伪令牌,然后 如果需要,请从缓存中删除令牌,或使t过期
[HttpPost]
public virtual ActionResult Order(OrderViewModel vModel)
{
     if (this.IsResubmit(vModel)) //  << Check Resubmit
     {
         ViewBag.ErrorMsg = "Form is Resubmitting";
     }
     else
     {
        // .... Post codes here without any changes...
     }

     this.PreventResubmit(vModel);// << Fill TempData & ViewModel PreventResubmit Property

     return View(vModel)
 }
@if (!string.IsNullOrEmpty(ViewBag.ErrorMsg))
{
    <div>ViewBag.ErrorMsg</div>
}

@using (Html.BeginForm(...)){

    @Html.HiddenFor(x=>x.PreventResubmit) // << Put this Hidden Field in the form

    // Others codes of the form without any changes
}
public class OrderViewModel: NoResubmitAbstract // << Inherit from NoResubmitAbstract 
{
     // Without any changes!
}
public static class ControllerExtentions
{
    [NonAction]
    public static bool IsResubmit (this System.Web.Mvc.ControllerBase controller, NoResubmitAbstract vModel)
    {
        return (Guid)controller.TempData["PreventResubmit"]!= vModel.PreventResubmit;
    }

    [NonAction]
    public static void PreventResubmit(this System.Web.Mvc.ControllerBase controller, params NoResubmitAbstract[] vModels)
    {
        var preventResubmitGuid = Guid.NewGuid();
        controller.TempData["PreventResubmit"] = preventResubmitGuid ;
        foreach (var vm in vModels)
        {
            vm.SetPreventResubmit(preventResubmitGuid);
        }
    }
}
public abstract class NoResubmitAbstract
{
    public Guid PreventResubmit { get; set; }

    public void SetPreventResubmit(Guid prs)
    {
        PreventResubmit = prs;
    }
}