我最近在ASP.NET MVC
应用程序中发布了多个表单的问题。情况基本上是,如果有人故意敲定提交按钮,他们可以强制数据被多次发布,尽管验证逻辑(服务器和客户端)都是为了禁止这一点。这是因为他们的帖子会在Transaction.Commit()
方法在初始请求上运行之前完成(这都是在nHibernate中完成的)
MVC ActionMethod看起来有点像..
public ActionResult Create(ViewModelObject model)
{
if(ModelState.IsValid)
{
// ...
var member = membershipRepository.GetMember(User.Identity.Name);
// do stuff with member
// update member
}
}
提出了很多解决方案,但是我找到了C#lock
语句并尝试了一下,所以我将代码改为看起来像这样......
public ActionResult Create(ViewModelObject model)
{
if(ModelState.IsValid)
{
// ...
var member = membershipRepository.GetMember(User.Identity.Name);
lock(member) {
// do stuff with member
// update member
}
}
}
有效!我的测试人员都不能重现这个bug了!我们一直在抨击它超过一天,没有人能找到任何缺陷。但我对这个关键字并不是那么经验。我再次查看以澄清......
lock关键字通过获取给定对象的互斥锁,执行语句,然后释放锁来将语句块标记为关键部分
好的,这是有道理的。这是我的问题。
此解决方案看似简单,直接,清晰,高效且干净。 方式太简单了。我知道比认为复杂的东西更简单的解决方案更好。所以我想问更有经验的程序员......
答案 0 :(得分:7)
不,不是那么容易。只有在使用相同的实例时,锁定才有效。
这不起作用:
public IActionResult Submit(MyModel model)
{
lock (model)
{
//will not block since each post generates it's own instance
}
}
您的示例可能有效。这完全取决于是否在nhibernate中启用了二级缓存(从而返回相同的用户实例)。请注意,它不会阻止任何内容发布到数据库,只是每个帖子都会按顺序保存。
另一种解决方案是在按下时将return false;
添加到提交按钮。它会阻止按钮多次提交表单。
这是一个jquery脚本,它将为您解决问题(它将遍历所有提交按钮,并确保它们只提交一次)
$(document).ready(function(){
$(':submit').click(function() {
var $this = $(this);
if ($this.hasClass('clicked')) {
alert('You have already clicked on submit, please be patient..');
return false;
}
$this.addClass('clicked');
});
});
添加它是布局还是javascript文件。
请注意,jquery代码在大多数情况下都有效,但请记住,任何具有一点编程知识的用户都可以使用例如HttpWebRequest
来向您的Web服务器发送垃圾邮件。这不太可能,但可能会发生。我要说的是,你不应该依赖客户端代码来处理问题,因为它们可以被规避。
答案 1 :(得分:0)
这很容易,但要小心锁定的对象。对于所有线程,它应该是相同的 - 例如,它可以是静态对象
lock
是Monitor的语法糖,所以在封面下有相当多的内容。
另外,你应该留意deadlocks - 当你锁定两个或多个物体时会发生这种情况。
答案 2 :(得分:0)
是的,这很容易,但是 - 可能会有性能损失。请记住,Monitor锁限制该代码一次只能由一个线程运行。每个HTTP请求都有一个新线程,因此这意味着在任何给定时间只有其中一个请求可以访问该代码。如果这是一个漫长的程序,或者很多人试图同时访问该网站的这一部分 - 你可能会开始缓慢的回应。