Как предотвратить многократную отправку форм в .NET MVC без использования Javascript? - PullRequest
47 голосов
/ 23 ноября 2010

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

Ответы [ 12 ]

49 голосов
/ 27 сентября 2015

Сначала убедитесь, что вы используете 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();
36 голосов
/ 23 ноября 2010

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

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

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

Это должно учитывать различия в браузере.

29 голосов
/ 23 марта 2011

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

$('#myform').submit(function () {
    if ($(this).find('.input-validation-error').length == 0) {
        $(this).find(':submit').attr('disabled', 'disabled');
    }
});
9 голосов
/ 28 июля 2014

что если мы используем $ (Это) .valid ()

 $('form').submit(function () {
            if ($(this).valid()) {
                $(this).find(':submit').attr('disabled', 'disabled');
            }
        });
4 голосов
/ 23 ноября 2010

Не изобретай велосипед :)

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

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

2 голосов
/ 23 ноября 2010

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

0 голосов
/ 13 июля 2019

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

$('input[type=submit]').click(function () {
    var clickedBtn = $(this)
    setTimeout(function () {
        clickedBtn.attr('disabled', 'disabled');
    }, 1);
});
0 голосов
/ 17 января 2019

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

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

0 голосов
/ 17 августа 2018

Вы можете сделать это, создав некоторый статический флаг ввода, который зависит от пользователя или от того, каким образом вы хотите защитить ресурс.Я использую 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 - это ответ на запрос двойного щелчка, который ничего не даст на стороне браузера.Когда медленный процесс завершен, браузер получит правильный ответ на исходный запрос и будет действовать соответственно.

0 голосов
/ 29 октября 2014

Это работает в любом браузере

 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;
                }
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...