2010-01-21 20 views
8

Tengo una página de compra y no quiero que el usuario pueda actualizar la página y volver a enviar el formulario una vez que llega a la página de 'orden completa' porque lo configura automáticamente en nuestro sistema a través de los valores y cargos de la base de datos. su tarjeta a través de PayPal (solo quiero que esto suceda UNA VEZ) ... He visto algunos sitios que dicen 'No pulse actualizar o se le cobrará dos veces'. pero eso es bastante cojo para dejarlo abierto a la posibilidad, ¿cuál es una buena manera de permitir que se envíe solo una vez o evitar que se refresquen, etc.?¿Cuál es un buen método para evitar que un usuario envíe un formulario dos veces?

PD: Vi algunas preguntas similares: PHP: Stop a Form from being accidentally reprocessed when Back is pressed y How do I stop the Back and Refresh buttons from resubmitting my form? pero no encontré una respuesta satisfactoria ... una respuesta específica de ASP.NET MVC sería ideal también si hay un mecanismo para esto.

EDITAR: Una vez que hacen clic en enviarlo POSTES a mi controlador y luego el controlador hace algo de magia y luego devuelve una vista con un mensaje de orden completa, pero si hago clic en actualizar en mi navegador hace todo el '¿quieres ¿Volver a enviar esta forma? eso es malo ...

+0

Usted puede tener más respuestas, si etiquetas como independiente del idioma, como no es solo ASP.NET MVC el que se ve afectado, sino que todos los lenguajes y marcos interactúan con los formularios web. (Por supuesto, puede obtener código si deja allí la etiqueta ASP.NET MVC). – Macha

Respuesta

17

La solución estándar para esto es el patrón POST/REDIRECT/GET. Este patrón se puede implementar usando casi cualquier plataforma de desarrollo web. Lo más habitual es:

  • presentación Validar después de POST
  • si no volver a hacer la solicitud de inscripción original con los errores de validación está representada
  • si tiene éxito, redirigir a una página de confirmación, o en la página donde se re- mostrar la entrada - esta es la parte GET
  • ya que la última acción fue un GET, si el usuario se actualiza en este punto, no hay ninguna remisión de formulario para ocurrir.
+6

Que, en ASP.Net MVC, se ejecuta simplemente haciendo un "retorno RedirectToAction()". – womp

+0

¿Cómo evitaría que una acción que no sea un redireccionamiento de una acción de manejo de solicitud de formulario golpeara esa acción? – Maritim

+0

@Maritim Nunca tuve que hacer esto. Normalmente estoy volviendo a una página de vista estándar, o una página de "agradecimiento", pero podría pasar un parámetro de querystring que la página espera, o establecer una variable de sesión para permitir que la próxima solicitud ingrese. – RedFilter

2

Mientras sirve la página de confirmación de pedido, puede establecer un token que también almacena en la base de datos/caché. En la primera instancia de confirmación de pedido, verifique la existencia de este token y borre el token. Si se implementa con seguridad de subprocesos, no podrá enviar el pedido dos veces.

Este es solo uno de los muchos enfoques posibles.

+0

Como una ventaja adicional, esta solución también evita el envío de formularios duplicados causados ​​por el usuario al presionar dos veces el botón Enviar. – Lex

1

Entregue a cada visitante un ID único cuando la página se cargue por primera vez. Tenga en cuenta la ID cuando se envía el formulario. Una vez que se haya enviado un formulario con esa ID, no permita ninguna otra solicitud que lo use. Si hacen clic en actualizar, se enviará la misma ID.

-1

En la parte superior de mi cabeza, genere un System.Guid en un campo oculto en la solicitud GET de la página y asócielo con su pago/pago. Simplemente verifíquelo y muestre un mensaje que diga 'Pago ya procesado'. o tal.

0

Simplemente haga un redireccionamiento desde la página que hace todas las cosas desagradables a la página "Gracias por su pedido". Una vez hecho esto, el usuario puede presionar actualizar tantas veces como quiera.

-1

Kazi Manzur Rashid escribió sobre this (junto con otras mejores prácticas de asp.net mvc). Sugiere usar dos filtros para manejar la transferencia de datos entre POST y FETwing GET usando TempData.

12

Estoy 100% de acuerdo con la respuesta genérica de RedFilter, pero quería publicar un código relevante específicamente para ASP.NET MVC.

Puede usar el Post/Redirect/Get (PRG) Pattern para resolver el problema de la doble devolución de datos.

Aquí es una ilustración gráfica del problema:

Diagram

Lo que pasa es que los accesos de los usuarios actualicen, el navegador intenta volver a presentar la última solicitud que hizo. Si la última solicitud fue una publicación, el navegador intentará hacer eso.

mayoría de los navegadores saben que esto no es normalmente lo que el usuario quiere hacer, por lo que pedirán de forma automática:

Chrome - La página que está buscando ha utilizado la información que ha introducido. Volver a esa página puede provocar que se repita cualquier acción que haya realizado. ¿Desea continuar?
Firefox - Para mostrar esta página, Firefox debe enviar información que repita cualquier acción (como una búsqueda o confirmación de pedido) que se haya realizado anteriormente.
Safari - ¿Estás seguro de que quieres enviar un formulario nuevamente? Para volver a abrir esta página, Safari debe volver a enviar un formulario. Esto podría dar lugar a compras duplicadas, comentarios u otras acciones.
Internet Explorer - Para volver a mostrar la página web, el navegador web necesita volver a enviar la información que ha enviado anteriormente. Si estaba haciendo una compra, debe hacer clic en Cancelar en para evitar una transacción duplicada. De lo contrario, haga clic en Reintentar para mostrar la página web nuevamente.

Pero el patrón PRG ayuda a evitar esto por completo mediante el envío al cliente un mensaje de redirección así que cuando finalmente aparece la página, la última solicitud del navegador ejecuta era una petición GET para el nuevo recurso.

Aquí hay un great article on PRG que proporciona una implementación del patrón para MVC. Es importante tener en cuenta que solo desea recurrir a una redirección cuando se realiza una acción que no es idempotent en el servidor. En otras palabras, si tiene un modelo válido y realmente ha persistido en los datos de alguna manera, entonces es importante asegurarse de que la solicitud no se envíe accidentalmente dos veces. Pero si el modelo no es válido, se debe devolver la página y el modelo actuales para que el usuario pueda realizar las modificaciones necesarias.

He aquí un controlador ejemplo:

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

Tenga en cuenta que el patrón PRG no protege completamente contra múltiples envíos de formularios, como las solicitudes de correos múltiples pueden ser despedidos fuera incluso antes de que un solo redirección ha tenido lugar - esto puede llevar a los envíos de su formulario que no sean idempotent.

hacer tomar nota de la respuesta que se ha proporcionado here, que proporciona una solución a este problema, que cito aquí por conveniencia:

Si usted hace uso de un token anti-falsificación escondido en su formulario (como lo debe ), puede almacenar en caché el token antifalsificación en el primer envío y eliminar el token de la memoria caché si es necesario, o caducar la entrada en caché después de establecer la cantidad de tiempo.

Luego podrá verificar con cada solicitud en contra del caché si el formulario específico ha sido enviado y rechazarlo si lo ha hecho.

No necesita generar su propio GUID, ya que esto ya se está haciendo al generar el token anti falsificación.

0

Si que no le gusta redirigir al usuario a otra página, a continuación, mediante el uso de mi camino no necesita dosis Mensaje/Redirigir/Obtener (PRG) Patrón y el usuario permanecen en la página actual sin miedo a los efectos negativos de volver a enviar el formulario!

que utilizar un elemento de TempData y una Hidden field (una propiedad en el ViewModel del formulario) para mantener una misma Guid en ambos lados (Server/Client) y es mi signo para detectar si el formulario se vuelve a presentar por la actualización o no.

cara final de los códigos parece muy corto y simple:

Acción:

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

En Vista:

@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 
} 

En vista del modelo:

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

¿Qué opinas?


que hacen que sea sencillo escribiendo 2 clases:

  • NoResubmitAbstract clase abstracta
  • ControllerExtentions clase estática (una clase de extensión para 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; 
    } 
} 

Sólo hay que poner en su proyecto MVC y ejecutarlo ...;)

Cuestiones relacionadas