使用 .NET Core 避免重复 POST

Gui*_*ira 6 .net c# asp.net-core

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

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

为了做 POST,我在客户端使用 axios。但是如何在服务器端避免这种情况呢?

Chr*_*rdt 7

我前段时间也经历过这样的场景。我为它创建了一个操作过滤器,它使用防伪令牌

[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);
    }
}
Run Code Online (Sandbox Code Playgroud)

简单地在您的方法中使用它:

[HttpPost]
[PreventDoublePost]
public async Task<IActionResult> Edit(EditViewModel model)
{
    if (!ModelState.IsValid)
    {
        //PreventDoublePost Attribute makes ModelState invalid
    }
    throw new NotImplementedException();
}
Run Code Online (Sandbox Code Playgroud)

确保生成防伪造令牌,请参阅有关其如何适用于JavascriptAngular 的文档。


Chr*_*att 6

坦率地说,处理插入的并发性很困难。更新和删除之类的事情相对来说比较简单,因为您可以使用并发令牌。例如,在进行更新时,会添加 WHERE 子句来检查即将更新并发标记值的行。如果不匹配,则意味着自上次查询数据以来已更新,然后您可以实施某种恢复策略。

插入的工作方式不同,因为显然没有什么可比较的。最好的选择是为特定插入分配一些 id 的有点复杂的策略。这必须保留在表中的一列上,并且该列必须是唯一的。当您显示表单时,您可以设置一个具有唯一值的隐藏输入,例如Guid.NewGuid()。当用户提交时,该信息将被发回。然后,它会添加到您的实体中,并且当您保存时,它将设置在创建的行上。

现在假设用户双击提交按钮,发出两个几乎同时发生的请求。由于为两个请求提交相同的表单数据,因此两次提交中都存在相同的 id。第一个成功的最终会将记录保存到数据库中,而下一个最终会抛出异常。由于要保存 id 的列是唯一的,并且为两个请求发送了相同的 id,因此第二个请求将无法保存。此时,您可以捕获异常并进行一些恢复。

我个人的建议是使其对用户来说是无缝的。当您点击捕获时,您将查询实际使用该 id 插入的行,并返回该 id/数据。例如,假设这是一个结账页面,而您正在创建订单。完成后,您可能会将用户重定向到订单确认页面。因此,在请求失败时,您会查找实际创建的订单,然后使用该订单号/id 立即重定向到订单确认页面。对于用户而言,他们只是直接进入确认页面,而您的应用程序最终只插入了一个订单。无缝的。


小智 0

如果您使用关系数据库,最简单的方法是向填充数据的表添加唯一约束。如果这是不可能的或者数据库不是关系型的,并且您有单个服务器实例,则可以在应用程序代码中使用同步,即将实体的单个实例填充到数据库中,并通过使用诸如等的同步原语来典型地修改该实例lock。但这种方法有一个显着的缺点 - 如果您的 Web 应用程序有多个实例(例如在不同的服务器上),则它不起作用。您可以应用的另一种方法是使用版本控制方法 - 也就是说,您可以将修改的版本与数据一起保留,并在写入数据库之前进行读取(以便增加版本),并在数据库端打开乐观锁定(大多数数据库)支持这个)。