避免使用.NET Core进行重复的POST

时间:2019-03-21 17:25:05

标签: c# .net asp.net-core

我在.NET Core REST API中使用POST在数据库上插入数据。

在我的客户端应用程序中,当用户单击一个按钮时,我将禁用此按钮。但是有时候,由于某种原因,单击按钮的速度可能比禁用按钮的功能快。这样,用户可以双击按钮,然后两次发送POST,两次插入数据。

要执行POST,我在客户端使用axios。但是如何避免在服务器端出现这种情况?

5 个答案:

答案 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令牌,请参阅文档了解JavascriptAngular的工作方式。

答案 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();
}