防止在ASP.NET MVC页面上双重提交/发布

我希望得到一些关于我打算用来防止ASP.NET MVC 4应用程序中重复记录的方法的反馈,以及我对用户体验没有的影响。

Web表单有六个输入字段和一个保存按钮(以及一个取消按钮),用户最多可以在表单中填写10分钟。

一旦通过post提交字段,在使用新的Guid作为主键将数据记录在数据库表中之后,页面在成功/失败时被重定向到不同的页面。

要阻止用户多次按下保存按钮,但允许浏览器在已关闭的连接上重新发布请求,我打算在呈现表单时为新记录主键提供Guid作为隐藏输入字段。

如果重新发布,或者用户多次按下保存,数据库服务器将拒绝记录的第二个post,因为重复密钥,我可以检查并处理服务器端。

但这会给我带来更大的问题吗?

如果您在表单中使用隐藏的防伪标记(如您所愿),则可以在首次提交时缓存防伪标记,并在需要时从缓存中删除标记,或者在设定的时间后使缓存条目到期。

然后,您将能够针对缓存检查每个请求是否已提交特定表单,如果有,则拒绝它。

您不需要生成自己的GUID,因为在生成防伪令牌时已经完成了此操作。

UPDATE

在设计解决方案时,请记住,每个请求都将在其自己的单独线程中异步处理,甚至可能在完全不同的服务器/应用实例上处理。

因此,甚至在进行第一个高速缓存条目之前,完全有可能处理多个请求(线程)。 要解决此问题,请将缓存实现为队列。 在每次提交(发布请求)时,将机器名称/ ID和线程ID与防伪令牌…延迟写入几毫秒,然后检查缓存/队列中最旧的条目是否为防伪令牌对应。

此外,所有正在运行的实例都必须能够访问缓存(共享缓存)。

如果您愿意,可以使用某些jQuery阻止从客户端双击。

在您的HTML中,让您的提交按钮如下所示:

 Submit Form   

在JavaScript(jQuery)中:

 $('#submit-button').click(function() { $(this).hide(); $('#working-message').html('Working on that...'); $.post('/someurl', formData, function(data) { //success //redirect to all done page }).fail(function(xhr) { $('#submit-button').show(); $('#working-message').html('Submit failed, try again?'); }); }); // end on click 

这将在它甚至尝试提交之前隐藏按钮,因此用户无法单击两次。 这也显示了进度,并且在失败时允许他们重新提交。 您可能想要考虑在上面的代码中添加超时。

另一个替代方法是使用jquery来获取$('#form-id').submit() ,但是你无法像我已经完成的ajax调用一样轻松地跟踪进度。

编辑:出于安全原因,我仍然建议从服务器端的角度来看防止双重提交的方法。

您可以在客户端处理它。

创建一个overlay div,一个带display none的css类,一个大的z-index和一个jquery脚本,当用户按下提交按钮时显示div。

据我了解,当您最初渲染页面时,您计划将主键保留在隐藏的输入中。 显然这不是一个好主意。 首先,如果您在c#中使用Guid实现,那么它的字符串并将字符串作为主键并不是一个好主意( 请参阅此SO问题的答案 )。

您可以通过两种方式解决此问题。 首先,在第一次单击时禁用该按钮。 其次,在不依赖主键的情况下在代码中构建validation。

有时,只在客户端处理它是不应该的。 尝试生成表单的哈希代码并保存在缓存中(设置过期日期或类似的东西)。

算法类似于:

1-用户发帖

2-生成post的哈希值

3-检查缓存中的哈希值

4-已发布缓存? 抛出exception

5- Post不在缓存上? 将新哈希保存在缓存中并将post保存在数据库中

一个样品:

  //verify duplicate post var hash = Util.Security.GetMD5Hash(String.Format("{0}{1}", topicID, text)); if (CachedData.VerifyDoublePost(hash, Context.Cache)) throw new Util.Exceptions.ValidadeException("Alert! Double post detected."); 

缓存function可能是这样的:

  public static bool VerifyDoublePost(string Hash, System.Web.Caching.Cache cache) { string key = "Post_" + Hash; if (cache[key] == null) { cache.Insert(key, true, null, DateTime.Now.AddDays(1), TimeSpan.Zero); return false; } else { return true; } }