Избегайте дублирования POST с .NET Core - PullRequest
3 голосов
/ 21 марта 2019

Я использую POST в API .NET Core REST для вставки данных в базу данных.

В моем клиентском приложении, когда пользователь нажимает кнопку, я отключаю эту кнопку.Но иногда, по какой-то причине, нажатие кнопки может быть быстрее, чем функция кнопки отключения.Таким образом, пользователь может дважды щелкнуть по кнопке, и POST будет отправлен дважды, вставив данные дважды.

Для выполнения POST я использую axios на стороне клиента.Но как я могу избежать этого на стороне сервера?

Ответы [ 4 ]

1 голос
/ 21 марта 2019

Работа с параллелизмом со вставками сложная, прямо скажем.Такие вещи, как обновления и удаления, относительно тривиальны, так как вы можете использовать токены параллелизма.Например, при обновлении добавляется предложение WHERE для проверки строки, которая должна быть обновлена, для значения токена параллелизма.Если он не совпадает, это означает, что он был обновлен с момента последнего запроса данных, и затем вы можете реализовать какую-то стратегию восстановления.

Вставки не работают так же, потому что там, очевидно, еще ничего нетсравнить с.Ваша лучшая ставка - несколько запутанная стратегия присвоения некоторого идентификатора определенной вставке.Это должно быть сохранено для столбца в вашей таблице, и этот столбец должен быть уникальным.Когда вы отображаете форму, вы устанавливаете скрытый ввод с уникальным значением, например, Guid.NewGuid().Затем он будет отправлен обратно, когда пользователь отправит.Затем он добавляется к вашей сущности, и при сохранении он будет установлен в строке, которая была создана.

Теперь предположим, что пользователь дважды щелкнул кнопку отправки, запустив два почти одновременных запроса.Поскольку для обоих запросов отправляются одни и те же данные формы, в обоих представлениях присутствует один и тот же идентификатор.Тот, кто делает это первым, заканчивает тем, что сохраняет запись в базе данных, в то время как следующий заканчивает тем, что выбрасывал исключение.Поскольку столбец, в который сохраняется идентификатор, является уникальным, и один и тот же идентификатор был отправлен для обоих запросов, второй не удастся сохранить.На этом этапе вы можете поймать исключение и восстановить его каким-либо образом.

Моя личная рекомендация - сделать его понятным для пользователя.Когда вы нажимаете кнопку catch, вы запрашиваете строку, которая фактически была вставлена ​​с этим идентификатором, и вместо этого возвращаете этот идентификатор / данные.Например, скажем, это было для страницы оформления заказа, и вы создавали заказы.Вы, вероятно, собираетесь перенаправить пользователя на страницу подтверждения заказа после завершения.Таким образом, в случае неудачного запроса вы просматриваете фактически созданный заказ, а затем сразу же перенаправляете на страницу подтверждения заказа с этим номером / идентификатором заказа.Что касается пользователей, то они просто перешли прямо на страницу подтверждения, и в результате ваше приложение добавило только один заказ.Бесшовные.

0 голосов
/ 21 марта 2019

У меня был такой сценарий некоторое время назад. Я создал фильтр действий для него, который использует Anti Fogery Token :

[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);
    }
}

Просто используйте его на вашем методе:

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

Убедитесь, что вы сгенерировали токен Anti Fogery, см. Документацию о том, как он работает для Javascript или Angular .

0 голосов
/ 21 марта 2019

Каждый раз, когда я видел «двойное подчинение», у меня возникал очень старый и распространенный вопрос:

Проверьте ответы в постах выше.

Прежде чем вы решите проблему с сервером, вы должны остановить клиент от отправки дважды. Проверьте, используете ли вы jquery-validation.js и правильно ли вы настроили поведение своего клиента.

После этого продолжайте другие ответы.

0 голосов
/ 21 марта 2019

Если вы используете реляционную базу данных, самый простой способ - добавить уникальное ограничение в таблицу (таблицы), в которую вводятся данные. Если это невозможно или база данных не является реляционной, и у вас есть один экземпляр сервера, вы можете использовать синхронизацию внутри кода приложения, то есть сохранить один экземпляр сущности, который нужно заполнить в db, и изменить этот экземпляр по существу, используя примитивы синхронизации, такие как lock и т. д. Но у этого подхода есть существенный недостаток - он не работает, если существует несколько экземпляров вашего веб-приложения (например, на разных серверах). Другой подход, который вы можете применить, - это использование подхода к управлению версиями, то есть вы можете сохранить версию модификации вместе с вашими данными и читать перед записью в базу данных (для увеличения версии) с включенной оптимистической блокировкой на стороне базы данных (большинство баз данных). поддержать это).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...