Response.Redirect con POST invece di Get?


248

Abbiamo l'obbligo di inviare un modulo e salvare alcuni dati, quindi reindirizzare l'utente a una pagina fuori sede, ma nel reindirizzamento, è necessario "inviare" un modulo con POST, non GET.

Speravo che ci fosse un modo semplice per farlo, ma sto iniziando a pensare che non lo sia. Penso che ora devo creare un'altra semplice pagina, con solo il modulo che desidero, reindirizzare ad esso, popolare le variabili del modulo, quindi eseguire una chiamata body.onload a uno script che chiama semplicemente document.forms [0] .submit ( );

Qualcuno può dirmi se esiste un'alternativa? Potremmo aver bisogno di modificarlo più avanti nel progetto, e potrebbe diventare un po 'complicato, quindi se ci fosse un modo semplice potremmo fare tutto ciò che non dipende da altre pagine sarebbe fantastico.

Comunque, grazie per tutte le risposte.


In PHP, puoi inviare i dati POST con cURL. C'è qualcosa di paragonabile a .NET?
Brian Warshaw,

Penso che questa sia la risposta facile che stavi cercando. Non potevo credere quanto geniale sia ... stackoverflow.com/a/6062248/110549
JoeCool

@BrianWarshaw Trovo System.Net.Http.HttpClient msdn.microsoft.com/en-us/library/… molto intuitivo e veloce da usare.
Stoyan Dimov,

Risposte:


228

Per fare ciò è necessario capire come funzionano i reindirizzamenti HTTP. Quando lo usi Response.Redirect(), invii una risposta (al browser che ha effettuato la richiesta) con il codice di stato HTTP 302 , che indica al browser dove andare dopo. Per definizione, il browser lo farà tramite una GETrichiesta, anche se la richiesta originale era a POST.

Un'altra opzione è quella di utilizzare il codice di stato HTTP 307 , che specifica che il browser deve effettuare la richiesta di reindirizzamento allo stesso modo della richiesta originale, ma di richiedere all'utente un avviso di sicurezza. Per fare ciò, dovresti scrivere qualcosa del genere:

public void PageLoad(object sender, EventArgs e)
{
    // Process the post on your side   

    Response.Status = "307 Temporary Redirect";
    Response.AddHeader("Location", "http://example.com/page/to/post.to");
}

Sfortunatamente, questo non funzionerà sempre. Browser diversi implementano questo in modo diverso , poiché non è un codice di stato comune.

Purtroppo, a differenza degli sviluppatori Opera e FireFox, gli sviluppatori IE non hanno mai letto le specifiche e persino l'ultimo IE7 più sicuro reindirizzerà la richiesta POST dal dominio A al dominio B senza avvisi o finestre di dialogo di conferma! Safari agisce anche in modo interessante, mentre non genera una finestra di dialogo di conferma ed esegue il reindirizzamento, butta via i dati POST, cambiando efficacemente il reindirizzamento 307 nel più comune 302.

Quindi, per quanto ne so, l'unico modo per implementare qualcosa del genere sarebbe usare Javascript. Ci sono due opzioni che mi vengono in mente dalla parte superiore della mia testa:

  1. Creare il modulo e fare in modo che l' actionattributo punti al server di terze parti. Quindi, aggiungi un evento click al pulsante di invio che prima esegue una richiesta AJAX con il tuo server con i dati, e quindi permette al modulo di essere inviato al server di terze parti.
  2. Crea il modulo per pubblicare sul tuo server. Quando il modulo viene inviato, mostra all'utente una pagina che contiene un modulo con tutti i dati che vuoi trasmettere, tutti in input nascosti. Mostra solo un messaggio come "Reindirizzamento ...". Quindi, aggiungi un evento javascript alla pagina che invia il modulo al server di terze parti.

Dei due, sceglierei il secondo, per due motivi. Innanzitutto, è più affidabile del primo perché Javascript non è necessario per funzionare; per coloro che non lo hanno abilitato, puoi sempre rendere visibile il pulsante di invio per il modulo nascosto e chiedere loro di premerlo se impiega più di 5 secondi. In secondo luogo, è possibile decidere quali dati vengono trasmessi al server di terze parti; se usi solo il modulo mentre procede, passerai tutti i dati dei post, che non è sempre quello che desideri. Lo stesso vale per la soluzione 307, supponendo che abbia funzionato per tutti i tuoi utenti.

Spero che questo ti aiuti!


1
Modifica "aggiungi un evento clic al pulsante di invio" in "aggiungi un evento di invio al modulo". Esiste più di un metodo per inviare il modulo, ad esempio premendo Invio con qualsiasi input di testo focalizzato, per non parlare dell'invio programmatico.
temoto,

122

Puoi usare questo approccio:

Response.Clear();

StringBuilder sb = new StringBuilder();
sb.Append("<html>");
sb.AppendFormat(@"<body onload='document.forms[""form""].submit()'>");
sb.AppendFormat("<form name='form' action='{0}' method='post'>",postbackUrl);
sb.AppendFormat("<input type='hidden' name='id' value='{0}'>", id);
// Other params go here
sb.Append("</form>");
sb.Append("</body>");
sb.Append("</html>");

Response.Write(sb.ToString());

Response.End();

Di conseguenza, subito dopo che il client otterrà tutti gli html dal server, avrà luogo il caricamento dell'evento che innesca il modulo di invio e registra tutti i dati in postbackUrl definito.


9
+1 (ti aggiungerei di più se potessi). Questa è la risposta. Eloquente e al punto. Hai anche incluso tutto il codice per farlo invece di parlare a capofitto. L'ho usato in un iframe sulla mia pagina aspx e ha reso tutto perfettamente - nessun URL di riscrittura. Lavoro eccellente! SUGGERIMENTO per coloro che usano iframe: punto il mio iframe su un'altra pagina aspx che quindi esegue questo codice.
MikeTeeVee,

1
Funziona! L'unica cosa negativa che vedo è che se si preme il pulsante Indietro del browser, risponde nuovamente alla richiesta, quindi non è più possibile raggiungere la pagina precedente. C'è una soluzione per questo?
Mt. Schneiders,

4
C'è una ragione per cui il metodo supportato per fare questo sta usando un 307. Le azioni POST sono intese come transazioni idempotenti. Questa risposta è semplicemente un trucco che sembra funzionare e può essere facilmente bloccato dai browser in futuro.
Sam Rueby,

2
@sam buon punto. Come contro-argomento: secondo la risposta principale, lo sviluppatore di IE non ha nemmeno letto circa 307. Altri che lo hanno letto lo hanno implementato in modo errato. Ciò significa che 307 chiaramente è fonte di confusione per i furbi (supponendo che gli sviluppatori di browser siano intelligenti) e incline a errori di interpretazione. L'approccio sopra è chiaro, almeno per me e funziona in tutti i browser passati e presenti. Non troppo preoccupato per il futuro mentre noi sviluppatori combattiamo il passato (leggi IE7) e il presente in / out. IMHO, dato che tutti l'hanno capito correttamente, dovrebbero tenerlo così com'è. Quale sarebbe la logica per bloccarlo, in futuro?
so_mv,

2
In che modo questa non è effettivamente la stessa della seconda opzione di TGHW? Non stai anche potenzialmente chiedendo a qualcuno che non ti interessa introdurre una vulnerabilità XSS?
Scott,

33

Per questo viene utilizzato HttpWebRequest.

Al postback, crea un HttpWebRequest a terze parti e pubblica i dati del modulo, quindi, una volta fatto, puoi Response.Redirect dove vuoi.

Ottieni il vantaggio aggiuntivo di non dover nominare tutti i controlli del tuo server per creare il modulo di terze parti, puoi fare questa traduzione quando crei la stringa POST.

string url = "3rd Party Url";

StringBuilder postData = new StringBuilder();

postData.Append("first_name=" + HttpUtility.UrlEncode(txtFirstName.Text) + "&");
postData.Append("last_name=" + HttpUtility.UrlEncode(txtLastName.Text));

//ETC for all Form Elements

// Now to Send Data.
StreamWriter writer = null;

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";                        
request.ContentLength = postData.ToString().Length;
try
{
    writer = new StreamWriter(request.GetRequestStream());
    writer.Write(postData.ToString());
}
finally
{
    if (writer != null)
        writer.Close();
}

Response.Redirect("NewPage");

Tuttavia, se è necessario che l'utente visualizzi la pagina di risposta da questo modulo, l'unica opzione è utilizzare Server.Transfer e potrebbe non funzionare.


è quello che ho intenzione di utilizzare se desidero un modulo aggiuntivo rispetto al modulo asp.net. Devo inviare alcuni dati a un URL esterno che è per il pagamento sicuro 3d, quindi devo ottenere le informazioni restituite dalla richiesta. È questo il modo di farlo? Grazie
Barbaros Alp

Se hai bisogno che l'utente veda la risposta, puoi semplicemente rispedire il contenuto e le intestazioni appropriate. Potrebbe essere necessario modificare alcuni aspetti del risultato a seconda dell'uso delle risorse relative, ma è certamente possibile.
Thomas S. Trias,

6
L'utente potrebbe disporre di dati cookie che non verrebbero trasmessi tramite questo metodo, poiché ciò sta accadendo dal server.
Scott,

7

Questo dovrebbe rendere la vita molto più semplice. Puoi semplicemente usare il metodo Response.RedirectWithData (...) nella tua applicazione web facilmente.

Imports System.Web
Imports System.Runtime.CompilerServices

Module WebExtensions

    <Extension()> _
    Public Sub RedirectWithData(ByRef aThis As HttpResponse, ByVal aDestination As String, _
                                ByVal aData As NameValueCollection)
        aThis.Clear()
        Dim sb As StringBuilder = New StringBuilder()

        sb.Append("<html>")
        sb.AppendFormat("<body onload='document.forms[""form""].submit()'>")
        sb.AppendFormat("<form name='form' action='{0}' method='post'>", aDestination)

        For Each key As String In aData
            sb.AppendFormat("<input type='hidden' name='{0}' value='{1}' />", key, aData(key))
        Next

        sb.Append("</form>")
        sb.Append("</body>")
        sb.Append("</html>")

        aThis.Write(sb.ToString())

        aThis.End()
    End Sub

End Module

6

Qualcosa di nuovo in ASP.Net 3.5 è questa proprietà "PostBackUrl" dei pulsanti ASP. Puoi impostarlo sull'indirizzo della pagina in cui vuoi pubblicare direttamente, e quando si fa clic su quel pulsante, invece di postare di nuovo sulla stessa pagina come al solito, pubblica invece la pagina che hai indicato. Maneggevole. Assicurarsi che UseSubmitBehavior sia impostato anche su TRUE.


4

Ho pensato che potesse essere interessante condividere che heroku fa questo con SSO ai provider di componenti aggiuntivi

Un esempio di come funziona può essere visto nel sorgente con lo strumento "kensa":

https://github.com/heroku/kensa/blob/d4a56d50dcbebc2d26a4950081acda988937ee10/lib/heroku/kensa/post_proxy.rb

E può essere visto in pratica se si gira di JavaScript. Fonte della pagina di esempio:

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Heroku Add-ons SSO</title>
  </head>

  <body>
    <form method="POST" action="https://XXXXXXXX/sso/login">

        <input type="hidden" name="email" value="XXXXXXXX" />

        <input type="hidden" name="app" value="XXXXXXXXXX" />

        <input type="hidden" name="id" value="XXXXXXXX" />

        <input type="hidden" name="timestamp" value="1382728968" />

        <input type="hidden" name="token" value="XXXXXXX" />

        <input type="hidden" name="nav-data" value="XXXXXXXXX" />

    </form>

    <script type="text/javascript">
      document.forms[0].submit();
    </script>
  </body>
</html>

3

PostbackUrl può essere impostato sul pulsante asp per pubblicare su un'altra pagina.

se devi farlo in codebehind, prova Server.Transfer.


2

@Opaco,

Puoi ancora utilizzare HttpWebRequest, quindi indirizzare la risposta che ricevi alla risposta effettiva del flusso di output, questo servirebbe la risposta all'utente. L'unico problema è che eventuali URL relativi verrebbero interrotti.

Tuttavia, potrebbe funzionare.


2

Ecco cosa farei:

Inserisci i dati in un modulo standard (senza attributo runat = "server") e imposta l'azione del modulo da pubblicare nella pagina off-site di destinazione. Prima di inviare vorrei inviare i dati al mio server utilizzando un XmlHttpRequest e analizzare la risposta. Se la risposta significa che dovresti andare avanti con il POST offsite allora io (il JavaScript) procederei con il post altrimenti reindirizzerei a una pagina sul mio sito


Funziona, ma è importante notare che perdi tutte le funzionalità lato server ASP.NET.
senfo,

2

In PHP, puoi inviare i dati POST con cURL. C'è qualcosa di paragonabile a .NET?

Sì, HttpWebRequest, vedi il mio post qui sotto.


2

Il metodo GET (e HEAD) non dovrebbe mai essere usato per fare qualcosa che abbia effetti collaterali. Un effetto collaterale potrebbe essere l'aggiornamento dello stato di un'applicazione Web o l'addebito sulla carta di credito. Se un'azione ha effetti collaterali, si dovrebbe usare un altro metodo (POST).

Pertanto, un utente (o il suo browser) non dovrebbe essere ritenuto responsabile per qualcosa fatto da un GET. Se si verificassero effetti collaterali dannosi o costosi a seguito di un OTTENERE, ciò sarebbe colpa dell'applicazione Web, non dell'utente. Secondo le specifiche, un agente utente non deve seguire automaticamente un reindirizzamento a meno che non sia una risposta a una richiesta GET o HEAD.

Naturalmente, molte richieste GET hanno alcuni effetti collaterali, anche se si tratta solo di aggiungere un file di registro. L'importante è che l'applicazione, non l'utente, sia ritenuto responsabile di tali effetti.

Le sezioni pertinenti delle specifiche HTTP sono 9.1.1 e 9.1.2 e 10.3 .


1

Suggerisco di creare un HttpWebRequest per eseguire a livello di codice il POST e quindi reindirizzare dopo aver letto la risposta, se applicabile.


1

Codice copia-incollabile basato sul metodo di Pavlo Neyman

RedirectPost (string url, T bodyPayload) e GetPostData () sono per coloro che vogliono semplicemente scaricare alcuni dati fortemente tipizzati nella pagina di origine e recuperarli in quello di destinazione. I dati devono essere serializzabili da NewtonSoft Json.NET e ovviamente è necessario fare riferimento alla libreria.

Basta copiare e incollare nella tua pagina (e) o meglio ancora classe di base per le tue pagine e usarlo ovunque nella tua applicazione.

Il mio cuore va a tutti voi che dovrete ancora utilizzare i Web Form nel 2019 per qualsiasi motivo.

        protected void RedirectPost(string url, IEnumerable<KeyValuePair<string,string>> fields)
        {
            Response.Clear();

            const string template =
@"<html>
<body onload='document.forms[""form""].submit()'>
<form name='form' action='{0}' method='post'>
{1}
</form>
</body>
</html>";

            var fieldsSection = string.Join(
                    Environment.NewLine,
                    fields.Select(x => $"<input type='hidden' name='{HttpUtility.UrlEncode(x.Key)}' value='{HttpUtility.UrlEncode(x.Value)}'>")
                );

            var html = string.Format(template, HttpUtility.UrlEncode(url), fieldsSection);

            Response.Write(html);

            Response.End();
        }

        private const string JsonDataFieldName = "_jsonData";

        protected void RedirectPost<T>(string url, T bodyPayload)
        {
            var json = JsonConvert.SerializeObject(bodyPayload, Formatting.Indented);
            //explicit type declaration to prevent recursion
            IEnumerable<KeyValuePair<string, string>> postFields = new List<KeyValuePair<string, string>>()
                {new KeyValuePair<string, string>(JsonDataFieldName, json)};

            RedirectPost(url, postFields);

        }

        protected T GetPostData<T>() where T: class 
        {
            var urlEncodedFieldData = Request.Params[JsonDataFieldName];
            if (string.IsNullOrEmpty(urlEncodedFieldData))
            {
                return null;// default(T);
            }

            var fieldData = HttpUtility.UrlDecode(urlEncodedFieldData);

            var result = JsonConvert.DeserializeObject<T>(fieldData);
            return result;
        }

0

In genere, tutto ciò che ti serve è portare uno stato tra queste due richieste. In realtà c'è un modo davvero stravagante per farlo che non si basa su JavaScript (pensa <noscript />).

Set-Cookie: name=value; Max-Age=120; Path=/redirect.html

Con quel cookie lì, puoi nella seguente richiesta a /redirect.html recuperare le informazioni nome = valore, puoi archiviare qualsiasi tipo di informazione in questa stringa coppia nome / valore, fino a dire 4K di dati (limite tipico del cookie). Ovviamente dovresti evitare questo e memorizzare invece codici di stato e bit di flag.

Dopo aver ricevuto questa richiesta, in cambio risponderai con una richiesta di eliminazione per quel codice di stato.

Set-Cookie: name=value; Max-Age=0; Path=/redirect.html

Il mio HTTP è un po 'arrugginito Ho provato RFC2109 e RFC2965 per capire quanto sia veramente affidabile, preferibilmente vorrei che il cookie facesse il viaggio di andata e ritorno esattamente una volta, ma ciò non sembra possibile, anche i cookie di terze parti potrebbe essere un problema per te se ti stai trasferendo in un altro dominio. Questo è ancora possibile ma non indolore come quando fai cose all'interno del tuo dominio.

Il problema qui è la concorrenza, se un utente esperto utilizza più schede e riesce a intercalare un paio di richieste appartenenti alla stessa sessione (questo è molto improbabile, ma non impossibile) ciò può portare a incoerenze nella tua applicazione.

È il modo <noscript /> di fare viaggi di andata e ritorno HTTP senza URL e JavaScript privi di significato

Fornisco questo codice come un concetto di concetto: se questo codice viene eseguito in un contesto che non hai familiarità, penso che tu possa capire quale parte è cosa.

L'idea è che al momento del reindirizzamento chiami Riposiziona con un certo stato e l'URL che hai ricollocato chiama GetState per ottenere i dati (se presenti).

const string StateCookieName = "state";

static int StateCookieID;

protected void Relocate(string url, object state)
{
    var key = "__" + StateCookieName + Interlocked
        .Add(ref StateCookieID, 1).ToInvariantString();

    var absoluteExpiration = DateTime.Now
        .Add(new TimeSpan(120 * TimeSpan.TicksPerSecond));

    Context.Cache.Insert(key, state, null, absoluteExpiration,
        Cache.NoSlidingExpiration);

    var path = Context.Response.ApplyAppPathModifier(url);

    Context.Response.Cookies
        .Add(new HttpCookie(StateCookieName, key)
        {
            Path = path,
            Expires = absoluteExpiration
        });

    Context.Response.Redirect(path, false);
}

protected TData GetState<TData>()
    where TData : class
{
    var cookie = Context.Request.Cookies[StateCookieName];
    if (cookie != null)
    {
        var key = cookie.Value;
        if (key.IsNonEmpty())
        {
            var obj = Context.Cache.Remove(key);

            Context.Response.Cookies
                .Add(new HttpCookie(StateCookieName)
                { 
                    Path = cookie.Path, 
                    Expires = new DateTime(1970, 1, 1) 
                });

            return obj as TData;
        }
    }
    return null;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.