2010-01-21 13 views
8

Ho una pagina di acquisto e non desidero che l'utente sia in grado di aggiornare la pagina e inviare nuovamente il modulo una volta arrivati ​​alla pagina "ordine completato" perché li imposta automaticamente nel nostro sistema tramite i valori e gli addebiti del database la loro carta tramite paypal (vogliono solo che succeda una volta) ... Ho visto alcuni siti che dicono "Non colpire l'aggiornamento o ti verranno addebitati due volte!" ma è abbastanza difficile lasciarlo aperto alla possibilità, qual è un buon modo per consentire solo che venga inviato una sola volta o impedisca loro di aggiornarsi, ecc.?Qual è un buon metodo per impedire a un utente di inviare un modulo due volte?

PS: Ho visto alcune domande simili: PHP: Stop a Form from being accidentally reprocessed when Back is pressed e How do I stop the Back and Refresh buttons from resubmitting my form? ma non ho trovato risposta soddisfacente ... una risposta specifica ASP.NET MVC sarebbe ideale anche se esiste un meccanismo per questo.

MODIFICA: una volta che fanno clic su INVIO POSTA al mio controller e poi il controller fa un po 'di magia e poi restituisce una vista con un messaggio di ordine completo, ma se clicco su Aggiorna sul mio browser fa tutto' vuoi rispedire questo modulo? ' non è così ...

+0

si può ottenere più risposte, se si etichetta come indipendente dal linguaggio, come non è solo ASP.NET MVC a essere interessato, ma tutte le lingue e i framework interagiscono con i moduli Web. (Naturalmente, si potrebbe ottenere il codice se si lascia il tag ASP.NET MVC lì). – Macha

risposta

17

La soluzione standard a questo è il modello POST/REDIRECT/GET. Questo schema può essere implementato utilizzando praticamente qualsiasi piattaforma di sviluppo web. Si farebbe in genere:

  • Convalida presentazione dopo il POST
  • se fallisce ri-renderizzare il modulo di iscrizione originale con errori di convalida visualizzata
  • se riesce, reindirizzare a una pagina di conferma, o la pagina in cui si ri- visualizza l'input: questa è la parte GET
  • poiché l'ultima azione era un GET, se l'utente si aggiorna a questo punto, non si verifica alcuna nuova inoltro di modulo.
+6

Che, in ASP.Net MVC, viene eseguito semplicemente facendo "return RedirectToAction()". – womp

+0

Come eviterai che l'azione venga colpita da qualcosa di diverso da un reindirizzamento da un'azione di gestione di richieste di moduli? – Maritim

+0

@Maritim Non ho mai dovuto farlo. Di solito sto tornando a una pagina di visualizzazione standard o a una pagina di ringraziamento, ma potresti passare un parametro querystring che la pagina si aspetta, o impostare una variabile di sessione per consentire la successiva richiesta. – RedFilter

2

Mentre serve la pagina di conferma dell'ordine è possibile impostare un token che si memorizza anche nel DB/Cache. Alla prima istanza della conferma dell'ordine, verificare la presenza di questo token e cancellare il token. Se implementato con sicurezza thread, non sarà possibile inviare l'ordine due volte.

Questo è solo uno dei tanti approcci possibili.

+0

Come ulteriore vantaggio, questa soluzione impedisce anche l'invio di moduli duplicati causati dall'utente che preme il pulsante di invio due volte. – Lex

1

Assegna a ogni modulo di visitatore un ID univoco quando la pagina viene caricata per la prima volta. Annota l'ID quando il modulo viene inviato. Una volta che un modulo è stato inviato con tale ID, non consentire ulteriori richieste che lo utilizzano. Se fanno clic su Aggiorna, verrà inviato lo stesso ID.

-1

Fuori dalla mia testa, generare un System.Guid in un campo nascosto sulla richiesta GET della pagina e associarlo con il pagamento/pagamento. Basta controllarlo e visualizzare un messaggio che indica "Pagamento già elaborato". o così.

0

Basta fare un reindirizzamento dalla pagina che fa tutte le cose cattive alla pagina "Grazie per il tuo ordine". Fatto ciò, l'utente può eseguire il refresh tutte le volte che vuole.

-1

Kazi Manzur Rashid ha scritto circa this (insieme ad altre best practice di asp.net mvc). Suggerisce di utilizzare due filtri per gestire il trasferimento dei dati tra il POST e il follwing GET utilizzando TempData.

12

I 100% concorda con la risposta generica di RedFilter, ma desiderava pubblicare in modo specifico un codice rilevante per ASP.NET MVC.

È possibile utilizzare lo Post/Redirect/Get (PRG) Pattern per risolvere il problema del doppio postback.

Ecco un'illustrazione grafica del problema:

Diagram

ciò che accade è quando l'utente preme rinfrescano, il browser tenta di inviare di nuovo l'ultima richiesta fatta. Se l'ultima richiesta era un post, il browser tenterà di farlo.

maggior parte dei browser sanno che questo non è in genere ciò che l'utente vuole fare, quindi vi chiederà automaticamente:

Chrome - La pagina che stai cercando di accedere utilizzava informazioni inserite. Ritornare a quella pagina potrebbe causare la ripetizione di qualsiasi azione eseguita. Vuoi continuare?
Firefox - Per visualizzare questa pagina, è necessario che Firefox invii informazioni che ripeteranno qualsiasi azione (ad esempio una ricerca o una conferma dell'ordine) eseguita in precedenza.
Safari - Sei sicuro di voler inviare nuovamente un modulo? Per riaprire questa pagina, Safari deve inviare di nuovo un modulo. Ciò potrebbe comportare acquisti, commenti o altre azioni duplicati.
Internet Explorer - Per visualizzare nuovamente la pagina Web, il browser Web deve inviare nuovamente le informazioni inviate in precedenza. Se si effettuava un acquisto, è necessario fare clic su Annulla su per evitare una transazione duplicata. Altrimenti, fare clic su Riprova per visualizzare nuovamente la pagina Web .

Ma il modello PRG consente di evitare tutto questo inviando al client un messaggio di reindirizzamento in modo che quando la pagina viene finalmente visualizzata, l'ultima richiesta eseguita dal browser è stata una richiesta GET per la nuova risorsa.

Ecco uno great article on PRG che fornisce un'implementazione del modello per MVC. È importante notare che si desidera ricorrere a un reindirizzamento solo quando viene eseguita un'azione non idempotent sul server. In altre parole, se hai un modello valido e in effetti hai persistito i dati in qualche modo, è importante assicurarsi che la richiesta non venga inviata per errore due volte. Ma se il modello non è valido, la pagina corrente e il modello devono essere restituiti in modo che l'utente possa apportare le modifiche necessarie.

Ecco un controller esempio:

[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

Nota che il modello PRG non completamente guardia contro più l'invio di moduli, come più le richieste POST possono essere licenziati fuori prima ancora che un singolo reindirizzamento ha avuto luogo - questo può portare al modulo di invio non essendo idempotent.

di prendere atto della risposta che è stata fornita here, che fornisce una soluzione a questo problema, che cito qui per comodità:

Se si fa uso di un token anti-contraffazione nascosto nel vostro modulo (come è necessario ), è possibile memorizzare nella cache il token anti-contraffazione al primo invio e rimuovere il token dalla cache, se necessario, o scadere la voce cache dopo un determinato periodo di tempo.

Sarà quindi possibile verificare con ogni richiesta sulla cache se il modulo specifico è stato inviato e rifiutarlo se lo ha.

Non è necessario generare il proprio GUID poiché questo è già in esecuzione durante la generazione del token anti-contraffazione.

0

Se voi non piace reindirizzare l'utente a un'altra pagina, poi utilizzando il mio modo non la dose bisogno Post/Redirect/Get (PRG) del modello e l'utente rimangono su la pagina corrente senza paura degli effetti negativi della re-invio del modulo!

Io uso un elemento TempData e un Hidden field (una proprietà nel ViewModel della forma) per mantenere uno stesso Guid in entrambi i lati (Server/Client) ed è il mio segno di rilevare se il modulo viene reinvio per l'aggiornamento o meno.

faccia finale dei codici assomiglia molto breve e semplice:

Azione:

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

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

In vista del modello:

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

Cosa ne pensi?


mi rendono semplice, scrivendo 2 classe:

  • NoResubmitAbstract classe astratta
  • ControllerExtentions classe statica (una classe di prolunga per 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; 
    } 
} 

Basta metterli nel progetto MVC ed eseguirlo ...;)

Problemi correlati