Каков хороший способ запретить пользователю отправлять форму дважды? - PullRequest
11 голосов
/ 21 января 2010

У меня есть страница покупки, и я не хочу, чтобы пользователь мог обновить страницу и повторно отправить форму, как только он попадет на страницу «Заказать завершен», поскольку он автоматически настраивает их в нашей системе через значения базы данных и снимает деньги с их карты через PayPal (нужно только, чтобы это произошло ОДНАЖДЫ) ... Я видел сайты, на которых написано: «Не нажимайте кнопку« Обновить », иначе с вас будет взиматься плата дважды!». но это довольно грустно, если оставить его открытым для возможности. Каков хороший способ разрешить его отправку только один раз или предотвратить его обновление и т. д.?

PS: я видел несколько похожих вопросов: PHP: предотвращение случайной повторной обработки формы при нажатии Back и Как мне остановить кнопки Back и Refresh от повторной отправки моей формы? но не нашел удовлетворительного ответа ... для ASP.NET MVC тоже был бы идеальный ответ, если бы был механизм для этого.

РЕДАКТИРОВАТЬ: Как только они нажимают, отправьте его POSTS моему контроллеру, а затем контроллер совершит какое-то волшебство, а затем вернет представление с сообщением о завершении заказа, но если я нажму кнопку «Обновить» в моем браузере, это произойдет полностью. эта форма?' это плохо ...

Ответы [ 9 ]

19 голосов
/ 21 января 2010

Стандартным решением для этого является шаблон POST / REDIRECT / GET . Этот шаблон может быть реализован с использованием практически любой платформы веб-разработки. Вы обычно:

  • Подтвердить отправку после POST
  • если не получится, перерисовать исходную форму ввода с отображенными ошибками проверки
  • в случае успеха, перенаправить на страницу подтверждения или страницу, где вы повторно отображаете ввод - это часть GET
  • , поскольку последнее действие было GET, если пользователь обновляется в этот момент, повторная отправка формы не происходит.
15 голосов
/ 09 февраля 2015

Я на 100% согласен с общим ответом RedFilter, но хотел опубликовать соответствующий код для ASP.NET MVC, в частности.

Вы можете использовать шаблон Post / Redirect / Get (PRG) для решения проблемы двойной обратной передачи.

Вот графическая иллюстрация проблемы:

Diagram

Что происходит, когда пользователь нажимает кнопку обновления, браузер пытается повторно отправить последний запрос, который он сделал. Если последним запросом была запись, браузер попытается это сделать.

Большинство браузеров знают, что это обычно не то, что пользователь хочет сделать, поэтому автоматически спросят:

Хром - Страница, которую вы ищете, использовала введенную вами информацию. Возврат на эту страницу может привести к тому, что любое предпринятое вами действие будет повторено. Вы хотите продолжить?
Firefox - Чтобы отобразить эту страницу, Firefox должен отправить информацию, которая будет повторять любое действие (например, поиск или подтверждение заказа), которое было выполнено ранее.
Safari - Вы уверены, что хотите отправить форму еще раз? Чтобы снова открыть эту страницу, Safari должен повторно отправить форму. Это может привести к повторным покупкам, комментариям или другим действиям.
Internet Explorer - Чтобы снова отобразить веб-страницу, веб-браузер должен повторно отправьте информацию, которую вы ранее отправили. Если вы совершали покупку, нажмите «Отмена», чтобы избежать дублирования транзакции. В противном случае нажмите «Повторить», чтобы отобразить снова веб-страница.

Но шаблон PRG помогает избежать этого в целом, посылая клиенту сообщение о перенаправлении, поэтому, когда страница наконец появляется, последний запрос, выполненный браузером, был запросом GET для нового ресурса.

Вот отличная статья по PRG , которая предоставляет реализацию шаблона для MVC. Важно отметить, что вы хотите прибегать к перенаправлению только тогда, когда на сервере выполняется не идемпотентное действие. Другими словами, если у вас есть действительная модель и вы действительно сохранили данные каким-либо образом, важно убедиться, что запрос не был случайно отправлен дважды. Но если модель недействительна, текущая страница и модель должны быть возвращены, чтобы пользователь мог внести любые необходимые изменения.

Вот пример контроллера:

[HttpGet]
public ActionResult Edit(int id) {
    var model = new EditModel();
    //...
    return View(model);
}

[HttpPost]
public ActionResult Edit(EditModel model) {
    if (ModelState.IsValid) {
        product = repository.SaveOrUpdate(model);
        return RedirectToAction("Details", new { id = product.Id });
    }
    return View(model);
}

[HttpGet]
public ActionResult Details(int id) {
    var model = new DetailModel();
    //...
    return View(model);
}
2 голосов
/ 21 января 2010

При обслуживании страницы подтверждения заказа вы можете установить токен, который вы также сохраните в БД / Кэш. При первом подтверждении заказа проверьте наличие этого токена и очистите токен. Если реализовано с безопасностью потока, вы не сможете отправить заказ дважды.

Это только один из многих возможных подходов.

1 голос
/ 05 мая 2016

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

Обратите внимание на ответ, который был предоставлен здесь , который предоставляет обходной путь для этой проблемы, который я привожу здесь для удобства:

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

После этого вы сможете проверять каждый запрос на кеш была ли отправлена ​​конкретная форма и отклонить ее, если она есть.

Вам не нужно создавать свой собственный GUID, так как это уже делается при создании токена защиты от подделки.

1 голос
/ 21 января 2010

Дайте каждой форме посетителя уникальный идентификатор при первой загрузке страницы. Запишите идентификатор при отправке формы. После того, как форма была отправлена ​​с этим идентификатором, не разрешайте дальнейшие запросы с ее использованием. Если они нажмут кнопку «Обновить», будет отправлен тот же идентификатор.

0 голосов
/ 13 июня 2017

Если вам не нравится перенаправлять пользователя на другую страницу, , тогда , используя мой способ вам не нужно дозировать Post / Redirect / Get (PRG ) Шаблон и пользователь остаются на текущей странице, не опасаясь негативных последствий повторной отправки формы!

Я использую элемент TempData и Hidden field (свойство в ViewModel формы) для сохранения одинакового Guid в обеих сторонах (Server/Client), и это мой знак, чтобы определить, повторная отправка формы обновлением или нет.

Окончательный вид кодов выглядит очень коротким и простым:

Действие:

[HttpPost]
public virtual ActionResult Order(OrderViewModel vModel)
{
     if (this.IsResubmit(vModel)) //  << Check Resubmit
     {
         ViewBag.ErrorMsg = "Form is Resubmitting";
     }
     else
     {
        // .... Post codes here without any changes...
     }

     this.PreventResubmit(vModel);// << Fill TempData & ViewModel PreventResubmit Property

     return View(vModel)
 }

В представлении:

@if (!string.IsNullOrEmpty(ViewBag.ErrorMsg))
{
    <div>ViewBag.ErrorMsg</div>
}

@using (Html.BeginForm(...)){

    @Html.HiddenFor(x=>x.PreventResubmit) // << Put this Hidden Field in the form

    // Others codes of the form without any changes
}

Модель View:

public class OrderViewModel: NoResubmitAbstract // << Inherit from NoResubmitAbstract 
{
     // Without any changes!
}

Что вы думаете?


Я делаю это просто, написав 2 класса:

  • NoResubmitAbstract абстрактный класс
  • ControllerExtentions статический класс (класс расширения для System.Web.Mvc.ControllerBase)

ControllerExtentions:

public static class ControllerExtentions
{
    [NonAction]
    public static bool IsResubmit (this System.Web.Mvc.ControllerBase controller, NoResubmitAbstract vModel)
    {
        return (Guid)controller.TempData["PreventResubmit"]!= vModel.PreventResubmit;
    }

    [NonAction]
    public static void PreventResubmit(this System.Web.Mvc.ControllerBase controller, params NoResubmitAbstract[] vModels)
    {
        var preventResubmitGuid = Guid.NewGuid();
        controller.TempData["PreventResubmit"] = preventResubmitGuid ;
        foreach (var vm in vModels)
        {
            vm.SetPreventResubmit(preventResubmitGuid);
        }
    }
}

NoResubmitAbstract:

public abstract class NoResubmitAbstract
{
    public Guid PreventResubmit { get; set; }

    public void SetPreventResubmit(Guid prs)
    {
        PreventResubmit = prs;
    }
}

Просто поместите их в свой MVC-проект и запустите ...;)

0 голосов
/ 22 января 2010

Кази Манзур Рашид написал о этом (вместе с другими лучшими практиками asp.net mvc). Он предлагает использовать два фильтра для обработки передачи данных между POST и следующим GET с использованием TempData.

0 голосов
/ 21 января 2010

Просто сделайте перенаправление со страницы, которая делает все неприятные вещи, на страницу «Спасибо за ваш заказ». Сделав это, пользователь может нажать обновить столько раз, сколько ему захочется.

0 голосов
/ 21 января 2010

Вверху моей головы, сгенерируйте System.Guid в скрытом поле в запросе GET на странице и привяжите его к оформлению заказа / оплате. Просто проверьте его и отобразите сообщение «Платеж уже обработан». или такой.

...