我在.NET Core REST API中使用POST在数据库上插入数据。
在我的客户端应用程序中,当用户单击一个按钮时,我将禁用此按钮。但是有时候,由于某种原因,单击按钮的速度可能比禁用按钮的功能快。这样,用户可以双击按钮,然后两次发送POST,两次插入数据。
要执行POST,我在客户端使用axios。但是如何避免在服务器端出现这种情况?
答案 0 :(得分:1)
坦率地说,处理带有插入的并发非常困难。由于可以使用并发令牌,因此更新和删除之类的事情相对来说并不那么重要。例如,进行更新时,将添加WHERE子句以检查将要更新的并发令牌值的行。如果不匹配,则表示自上次查询数据以来已对其进行了更新,然后可以实施某种恢复策略。
插入的工作方式不同,因为显然尚无可比较的内容。最好的选择是为某些特定的插入分配一些ID的策略有些复杂。这将必须保留在表中的列上,并且该列必须是唯一的。显示表单时,您将设置一个隐藏的输入,该输入具有唯一值,例如Guid.NewGuid()
。然后,当用户提交时,它将被发回。然后将其添加到您的实体中,并在保存时将其设置在创建的行上。
现在,假设用户双击“提交”按钮,触发了两个几乎同时发生的请求。由于正在为两个请求提交相同的表单数据,因此在两个提交中都存在相同的ID。首先使它结束的是将记录保存到数据库,而最后使它抛出异常。由于ID被保存到的列是唯一的,并且两个请求都发送了相同的ID,因此第二个将无法保存。此时,您可以捕获异常并恢复一些方法。
我个人的建议是使其与用户无缝连接。当您遇到问题时,您将查询实际使用该ID插入的行,并返回该ID /数据。例如,假设这是一个结帐页面,而您正在创建订单。完成后,您可能会将用户重定向到订单确认页面。因此,在失败的请求上,您将查找实际创建的订单,然后直接使用该订单号/ id重定向到订单确认页面。就用户而言,他们只是直接转到确认页面,您的应用最终只插入了一个订单。无缝。
答案 1 :(得分:0)
如果使用关系数据库,最简单的方法是向填充数据的表添加唯一约束。如果这是不可能的,或者数据库不是关系的,并且您具有单个服务器实例,则可以在应用程序代码内部使用同步,即,将实体的单个实例填充到db中,并使用{{1 }}等。但是这种方法有很大的缺点-如果您的Web应用程序有多个实例(例如,在不同的服务器上),则无法使用。您可以应用的另一种方法是使用版本控制方法-即,您可以将修改的版本与数据一起保留,并在写入数据库(以增加版本)之前进行读取,同时在db端(大多数dbs)打开了乐观锁定。支持此)。
答案 2 :(得分:0)
每当我看到“重复提交”时,都会带给我一个非常古老且常见的问题:
检查以上帖子中的答案。
在解决服务器问题之前,必须先停止客户端两次提交。 检查您是否正在使用jquery-validation.js,以及是否正确配置了客户端行为。
此后继续其他答案。
答案 3 :(得分:0)
我前一段时间有这种情况。我为此创建了一个动作过滤器,该过滤器使用的是Anti Fogery Token:
>>> k=SomeClass([1,2],[3,4])
>>> k.x
[1, 2]
>>> j=SomeClass([2,2],[4,4])
>>> j.x
[2, 2]
>>> k.x
[2, 2]
在您的方法上简单使用它:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class PreventDoublePostAttribute : ActionFilterAttribute
{
private const string TokenSessionName = "LastProcessedToken";
public override void OnActionExecuting(ActionExecutingContext context)
{
var antiforgeryOptions = context.HttpContext.RequestServices.GetOption<AntiforgeryOptions>();
var tokenFormName = antiforgeryOptions.FormFieldName;
if (!context.HttpContext.Request.Form.ContainsKey(tokenFormName))
{
return;
}
var currentToken = context.HttpContext.Request.Form[tokenFormName].ToString();
var lastToken = context.HttpContext.Session.GetString(TokenSessionName);
if (lastToken == currentToken)
{
context.ModelState.AddModelError(string.Empty, "Looks like you accidentally submitted the same form twice.");
return;
}
context.HttpContext.Session.SetString(TokenSessionName, currentToken);
}
}
请确保您生成了Anti Fogery令牌,请参阅文档了解Javascript或Angular的工作方式。
答案 4 :(得分:-1)
此答案的灵感来自@Christian Gollhardt答案 首先,您需要在stratup.cs中添加会话
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = Context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMemoryCache();
services.AddSession(options => {
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromMinutes(10);
options.Cookie.HttpOnly = true;
// Make the session cookie essential
options.Cookie.IsEssential = true;
});
然后
app.UseSession();
然后上课
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class PreventDoublePostAttribute : ActionFilterAttribute
{
private const string UniqFormuId = "LastProcessedToken";
public override async void OnActionExecuting(ActionExecutingContext context)
{
IAntiforgery antiforgery = (IAntiforgery)context.HttpContext.RequestServices.GetService(typeof(IAntiforgery));
AntiforgeryTokenSet tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
if (!context.HttpContext.Request.Form.ContainsKey(tokens.FormFieldName))
{
return;
}
var currentFormId = context.HttpContext.Request.Form[tokens.FormFieldName].ToString();
var lastToken = "" + context.HttpContext.Session.GetString(UniqFormuId);
if (lastToken.Equals(currentFormId))
{
context.ModelState.AddModelError(string.Empty, "Looks like you accidentally submitted the same form twice.");
return;
}
context.HttpContext.Session.Remove(UniqFormuId);
context.HttpContext.Session.SetString(UniqFormuId, currentFormId);
await context.HttpContext.Session.CommitAsync();
}
}
用法
[HttpPost]
[PreventDoublePost]
public async Task<IActionResult> Edit(EditViewModel model)
{
if (!ModelState.IsValid)
{
//PreventDoublePost Attribute makes ModelState invalid
}
throw new NotImplementedException();
}