C# 防止用户两次提交表单的好方法是什么?
我有一个购买页面,我不希望用户在进入“订单完成”页面后能够刷新页面并重新提交表单,因为它会通过数据库值在我们的系统中自动设置它们,并通过paypal向它们的卡收费(只希望发生一次)。。。我见过一些网站说“不要点击刷新,否则你会被收取两次费用!”但这是相当跛脚,让它开放的可能性,什么是一个好办法,只允许它提交一次或防止他们刷新,等等 PS:我看到了一些类似的问题,但没有找到令人满意的答案。。。如果有这样的机制,ASP.NETMVC特定的答案也是理想的C# 防止用户两次提交表单的好方法是什么?,c#,asp.net-mvc,form-submit,C#,Asp.net Mvc,Form Submit,我有一个购买页面,我不希望用户在进入“订单完成”页面后能够刷新页面并重新提交表单,因为它会通过数据库值在我们的系统中自动设置它们,并通过paypal向它们的卡收费(只希望发生一次)。。。我见过一些网站说“不要点击刷新,否则你会被收取两次费用!”但这是相当跛脚,让它开放的可能性,什么是一个好办法,只允许它提交一次或防止他们刷新,等等 PS:我看到了一些类似的问题,但没有找到令人满意的答案。。。如果有这样的机制,ASP.NETMVC特定的答案也是理想的 编辑:一旦他们点击submit,它就会发布到我
编辑:一旦他们点击submit,它就会发布到我的控制器,然后控制器会做一些魔术,然后返回一个带有订单完成消息的视图,但是,如果我在浏览器上单击“刷新”,它会执行整个“是否要重新发送此表单?”操作,这很糟糕…在提供订单确认页面时,您可以设置一个令牌,并将其存储在DB/缓存中。在订单确认的第一个实例中,检查此令牌是否存在并清除该令牌。如果使用线程安全实现,您将无法提交两次订单
这只是众多可能的方法之一 首次加载页面时,为每个访问者的表单提供一个唯一的ID。提交表单时请注意ID。一旦使用该ID提交表单,就不允许使用该ID的任何进一步请求。如果他们单击“刷新”,将发送相同的ID。在我的头顶上,在页面的GET请求的隐藏字段中生成一个
System.Guid
,并将其与结帐/付款关联。只需检查它,并显示一条消息,说明“已处理付款”或诸如此类。这方面的标准解决方案是模式。这种模式几乎可以使用任何web开发平台来实现。您通常会:
- 发布后验证提交
- 如果失败,则重新呈现原始输入表单,并显示验证错误
- 如果成功,则重定向到确认页面,或重新显示输入的页面-这是获取部分
- 由于上一个操作是GET,因此如果用户此时刷新,则不会发生表单重新提交
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;
}
}