Подтвердить что ты не робот

Как предотвратить множественную отправку формы в .NET MVC без использования Javascript?

Я хочу, чтобы пользователи не отправляли формы несколько раз в .NET MVC. Я пробовал несколько методов с помощью Javascript, но у меня были трудности с его работой во всех браузерах. Итак, как я могу предотвратить это в своем контроллере? Есть ли способ обнаружить несколько сообщений?

4b9b3361

Ответ 1

Не изобретайте колесо:)

Используйте Post/Redirect/Get шаблон дизайна.

Здесь вы можете найти question и ответить на некоторые предложения о том, как реализовать его в ASP.NET MVC.

Ответ 2

Во-первых, убедитесь, что вы используете AntiForgeryToken в своей форме.

Затем вы можете сделать собственный ActionFilter:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class PreventDuplicateRequestAttribute : ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext filterContext) {
        if (HttpContext.Current.Request["__RequestVerificationToken"] == null)
            return;

        var currentToken = HttpContext.Current.Request["__RequestVerificationToken"].ToString();

        if (HttpContext.Current.Session["LastProcessedToken"] == null) {
            HttpContext.Current.Session["LastProcessedToken"] = currentToken;
            return;
        }

        lock (HttpContext.Current.Session["LastProcessedToken"]) {
            var lastToken = HttpContext.Current.Session["LastProcessedToken"].ToString();

            if (lastToken == currentToken) {
                filterContext.Controller.ViewData.ModelState.AddModelError("", "Looks like you accidentally tried to double post.");
                return;
            }

            HttpContext.Current.Session["LastProcessedToken"] = currentToken;
        }
    }
}

И на вашем контроллере действия вы просто...

[HttpPost]
[ValidateAntiForgeryToken]
[PreventDuplicateRequest]
public ActionResult CreatePost(InputModel input) {
   ...
}

Вы заметите, что это не мешает запросу в целом. Вместо этого он возвращает ошибку в состояние модели, поэтому, когда ваше действие проверяет, есть ли ModelState.IsValid оно увидит, что это не так, и вернется с вашей обычной обработкой ошибок.


ОБНОВЛЕНИЕ: здесь решение ASP.NET Core MVC

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

Новый помощник по тегам формы теперь автоматически включает AntiForgeryToken, поэтому вам больше не нужно вручную добавлять его в свое представление.

Создайте новый ActionFilterAttribute как в этом примере. С этим вы можете сделать много других вещей, например, проверку задержки, чтобы убедиться, что даже если пользователь представляет два разных токена, они не отправляют несколько раз в минуту.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class PreventDuplicateRequestAttribute : ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext context) {
        if (context.HttpContext.Request.Form.ContainsKey("__RequestVerificationToken")) {
            var currentToken = context.HttpContext.Request.Form["__RequestVerificationToken"].ToString();
            var lastToken = context.HttpContext.Session.GetString("LastProcessedToken");

            if (lastToken == currentToken) {
                context.ModelState.AddModelError(string.Empty, "Looks like you accidentally submitted the same form twice.");
            }
            else {
                context.HttpContext.Session.SetString("LastProcessedToken", currentToken);
            }
        }
    }
}

По запросу я также написал асинхронную версию, которую можно найти здесь.

Вот надуманный пример использования пользовательского атрибута PreventDuplicateRequest.

[HttpPost]
[ValidateAntiForgeryToken]
[PreventDuplicateRequest]
public IActionResult Create(InputModel input) {
    if (ModelState.IsValid) {
        // ... do something with input

        return RedirectToAction(nameof(SomeAction));
    }

    // ... repopulate bad input model data into a fresh viewmodel

    return View(viewModel);
}

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

Примечание по настройке: Core MVC не имеет сессий, включенных по умолчанию. Вам нужно будет добавить пакет Microsoft.AspNet.Session в ваш проект и правильно настроить файл Startup.cs. Пожалуйста, прочитайте эту статью для более подробной информации.

Краткая версия настройки сеанса: В Startup.ConfigureServices() вам необходимо добавить:

services.AddDistributedMemoryCache();
services.AddSession();

В Startup.Configure() вам нужно добавить (перед app.UseMvc() !!):

app.UseSession();

Ответ 3

Я пробовал несколько методов с помощью Javascript, но у меня были трудности с его работой во всех браузерах.

Вы пытались использовать jquery?

$('#myform').submit(function() {
    $(this).find(':submit').attr('disabled', 'disabled');
});

Это должно позаботиться о различиях браузера.

Ответ 4

Чтобы завершить ответ @Darin, если вы хотите обработать проверку клиента (если форма имеет обязательные поля), вы можете проверить, есть ли ошибка проверки ввода перед отключением кнопки отправки:

$('#myform').submit(function () {
    if ($(this).find('.input-validation-error').length == 0) {
        $(this).find(':submit').attr('disabled', 'disabled');
    }
});

Ответ 5

что, если мы будем использовать  $ (Это).valid()

 $('form').submit(function () {
            if ($(this).valid()) {
                $(this).find(':submit').attr('disabled', 'disabled');
            }
        });

Ответ 6

Вы можете включить в сообщение формы скрытое (случайное или счетное) значение, контроллер может отслеживать эти значения в "открытом" списке или что-то подобное; каждый раз, когда ваш контроллер передает форму, он вводит значение, которое он отслеживает, позволяя использовать одно сообщение для него.

Ответ 7

В своем "я", нет, однако, в зависимости от того, что делает контроллер, вы должны уметь выработать способ.

Создана ли запись в базе данных, которую вы можете проверить, если она уже отправила форму?

Ответ 8

Вы также можете передать какой-то токен в скрытом поле и проверить его в контроллере.

Или вы работаете с перенаправлением после отправки значений. Но это становится затруднительным, если вы сильно пользуетесь ajax.

Ответ 9

Вы можете сделать это, создав некоторый статический флаг входа, который зависит от пользователя или от того, каким образом вы хотите защитить ресурс. Я использую ConcurrentDictionary для отслеживания входа. Ключ - это имя защищаемого ресурса в сочетании с идентификатором пользователя. Хитрость заключается в том, чтобы выяснить, как заблокировать запрос, если вы знаете, что он обрабатывается в данный момент.

public async Task<ActionResult> SlowAction()
{
    if(!CanEnterResource(nameof(SlowAction)) return new HttpStatusCodeResult(204);
    try
    {
        // Do slow process
        return new SlowProcessActionResult();
    }
    finally
    {
       ExitedResource(nameof(SlowAction));
    }
}

Возвращение 204 - это ответ на запрос двойного щелчка, который ничего не будет делать на стороне браузера. Когда медленный процесс завершен, браузер получит правильный ответ на исходный запрос и будет действовать соответственно.

Ответ 10

Используйте шаблон Post/Redirect/Get.

PS: мне кажется, что ответ Джима Ярбро может иметь фундаментальный недостаток в том, что __RequestVerificationToken хранится в HttpContext.Current.Session ["LastProcessedToken"], это значение будет заменено при отправке второй формы (от скажем, другое окно браузера), на этом этапе можно повторно отправить первую форму, и она не будет распознана как повторная отправка? Для того, чтобы предлагаемая модель работала, не требуется история __RequestVerificationToken (?), Это не осуществимо.

Ответ 11

Используйте это простое поле ввода jquery, и оно будет прекрасно работать, даже если у вас есть несколько кнопок отправки в одной форме.

$('input[type=submit]').click(function () {
    var clickedBtn = $(this)
    setTimeout(function () {
        clickedBtn.attr('disabled', 'disabled');
    }, 1);
});

Ответ 12

Просто добавьте этот код в конце своей страницы. Я использую "jquery-3.3.1.min.js" и "bootstrap 4.3.1"

<script type="text/javascript">
    $('form').submit(function () {
        if ($(this).valid()) {
            $(this).find(':submit').attr('disabled', 'disabled');
        }
    });
</script>

Ответ 13

Это работает в каждом браузере

 document.onkeydown = function () {
        switch (event.keyCode) {
            case 116: //F5 button
                event.returnValue = false;
                event.keyCode = 0;
                return false;
            case 82: //R button
                if (event.ctrlKey) {
                    event.returnValue = false;
                    event.keyCode = 0;
                    return false;
                }
        }
    }