Response.Redirect mit POST anstelle von Get?

248

Wir müssen ein Formular senden und einige Daten speichern und dann den Benutzer auf eine Seite außerhalb des Unternehmens umleiten. Bei der Umleitung müssen wir jedoch ein Formular mit POST "senden", nicht mit GET.

Ich hatte gehofft, dass es einen einfachen Weg gibt, dies zu erreichen, aber ich fange an zu denken, dass es keinen gibt. Ich denke, ich muss jetzt eine einfache andere Seite mit nur dem gewünschten Formular erstellen, zu diesem umleiten, die Formularvariablen füllen und dann einen body.onload-Aufruf an ein Skript ausführen, das lediglich document.forms [0] .submit ( );

Kann mir jemand sagen, ob es eine Alternative gibt? Möglicherweise müssen wir dies später im Projekt optimieren, und es kann etwas kompliziert werden. Wenn es also einfach wäre, könnten wir dies alles tun, was nicht von anderen Seiten abhängt, das wäre fantastisch.

Wie auch immer, danke für alle Antworten.

Matt Dawdy
quelle
In PHP können Sie POST-Daten mit cURL senden. Gibt es etwas Vergleichbares für .NET?
Brian Warshaw
Ich denke, das ist die einfache Antwort, nach der Sie gesucht haben. Ich konnte nicht glauben, wie genial es ist ... stackoverflow.com/a/6062248/110549
JoeCool
@BrianWarshaw Ich finde System.Net.Http.HttpClient msdn.microsoft.com/en-us/library/… sehr intuitiv und schnell zu bedienen.
Stoyan Dimov

Antworten:

228

Um dies zu tun, müssen Sie wissen, wie HTTP-Weiterleitungen funktionieren. Bei Verwendung Response.Redirect()senden Sie eine Antwort (an den Browser, der die Anforderung gestellt hat) mit dem HTTP-Statuscode 302 , der dem Browser mitteilt, wohin er als Nächstes gehen soll. Per Definition wird der Browser dies über eine GETAnfrage tun, selbst wenn die ursprüngliche Anfrage eine war POST.

Eine andere Option ist die Verwendung des HTTP-Statuscodes 307 , der angibt, dass der Browser die Umleitungsanforderung auf die gleiche Weise wie die ursprüngliche Anforderung ausführen soll, den Benutzer jedoch mit einer Sicherheitswarnung auffordert. Um das zu tun, würden Sie so etwas schreiben:

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");
}

Dies wird leider nicht immer funktionieren. Verschiedene Browser implementieren dies unterschiedlich , da es sich nicht um einen gemeinsamen Statuscode handelt.

Leider haben die IE-Entwickler im Gegensatz zu den Entwicklern von Opera und FireFox die Spezifikation nie gelesen, und selbst der neueste, sicherste IE7 leitet die POST-Anforderung ohne Warnungen oder Bestätigungsdialoge von Domäne A zu Domäne B um! Safari verhält sich auch auf interessante Weise, löst zwar keinen Bestätigungsdialog aus und führt die Umleitung durch, wirft jedoch die POST-Daten weg und ändert die 307-Umleitung effektiv in die allgemeinere 302.

Soweit ich weiß, wäre die einzige Möglichkeit, so etwas zu implementieren, die Verwendung von Javascript. Es gibt zwei Möglichkeiten, die mir auf den ersten Blick einfallen:

  1. Erstellen Sie das Formular und lassen Sie sein actionAttribut auf den Server eines Drittanbieters verweisen. Fügen Sie dann der Schaltfläche "Senden" ein Klickereignis hinzu, das zuerst eine AJAX-Anforderung mit den Daten an Ihren Server ausführt und dann das Senden des Formulars an den Server eines Drittanbieters ermöglicht.
  2. Erstellen Sie das Formular zum Posten auf Ihrem Server. Zeigen Sie dem Benutzer beim Senden des Formulars eine Seite mit einem Formular mit allen Daten, die Sie weitergeben möchten, und zwar in versteckten Eingaben. Zeigen Sie einfach eine Nachricht wie "Weiterleiten ...". Fügen Sie dann der Seite, die das Formular an den Server eines Drittanbieters sendet, ein Javascript-Ereignis hinzu.

Von den beiden würde ich aus zwei Gründen die zweite wählen. Erstens ist es zuverlässiger als das erste, da Javascript nicht erforderlich ist, damit es funktioniert. Für diejenigen, die es nicht aktiviert haben, können Sie die Senden-Schaltfläche für das ausgeblendete Formular jederzeit sichtbar machen und sie anweisen, es zu drücken, wenn es länger als 5 Sekunden dauert. Zweitens können Sie entscheiden, welche Daten an den Server eines Drittanbieters übertragen werden. Wenn Sie nur das Formular im Laufe der Zeit verarbeiten, geben Sie alle Post-Daten weiter, was nicht immer das ist, was Sie wollen. Gleiches gilt für die 307-Lösung, vorausgesetzt, sie funktioniert für alle Benutzer.

Hoffe das hilft!

tghw
quelle
1
Bitte ändern Sie "Klickereignis zur Senden-Schaltfläche hinzufügen" in "Senden-Ereignis zum Formular hinzufügen". Es gibt mehr als eine Methode zum Senden eines Formulars, z. B. Drücken der Eingabetaste bei fokussierter Texteingabe, geschweige denn programmgesteuertes Senden.
Temoto
122

Sie können diesen Ansatz verwenden:

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();

Als Ergebnis, unmittelbar nachdem der Client alle HTML- Dateien vom Server abgerufen hat , findet der Ereignis- Onload statt, der das Senden auslöst und alle Daten an die definierte postbackUrl sendet.

Pavlo Neiman
quelle
9
+1 (Ich würde dir mehr hinzufügen, wenn ich könnte). Das ist die Antwort. Beredsam und auf den Punkt. Sie haben sogar den gesamten Code dazu eingefügt, anstatt über den Kopf zu reden. Ich habe dies in einem Iframe auf meiner Aspx-Seite verwendet und es hat alles perfekt gerendert - kein Umschreiben von URLs. Ausgezeichnete Arbeit! TIPP für Benutzer von iframe: Ich zeige meinen iframe auf eine andere aspx-Seite, die diesen Code dann ausführt.
MikeTeeVee
1
Es funktioniert! Das einzig schlechte, was ich sehe, ist, dass wenn Sie auf die Zurück-Schaltfläche des Browsers klicken, die Anforderung erneut ausgeführt wird, sodass Sie die vorherige Seite nicht mehr wirklich erreichen können. Gibt es dafür einen Workarround?
Mt. Schneiders
4
Es gibt einen Grund, warum die unterstützte Methode hierfür die Verwendung eines 307 verwendet. POST-Aktionen sind als idempotente Transaktionen gedacht. Diese Antwort ist lediglich ein Hack, der zufällig funktioniert und in Zukunft sehr leicht von Browsern blockiert werden kann.
Sam Rueby
2
@sam guter Punkt. Als Gegenargument: Gemäß der Top-Antwort hat der IE-Entwickler nicht einmal über 307 gelesen. Andere, die es gelesen haben, haben es falsch implementiert. Das bedeutet, dass 307 für die Smarts eindeutig verwirrend ist (vorausgesetzt, Browser-Entwickler sind Smarts) und anfällig für Interpretationsfehler ist. Der obige Ansatz ist zumindest für mich klar und funktioniert in allen Browsern der Vergangenheit und Gegenwart. Keine allzu große Sorge um die Zukunft, da wir Entwickler gegen die Vergangenheit (siehe IE7) und die Gegenwart kämpfen. IMHO, da jeder es richtig verstanden hat, sollte er es so lassen, wie es ist. Was wäre der Grund, um es in Zukunft zu blockieren?
so_mv
2
Wie ist dies nicht effektiv dasselbe wie die zweite Option von TGHW? Bitten Sie nicht möglicherweise auch jemanden, der nachlässig ist, eine XSS-Sicherheitsanfälligkeit einzuführen?
Scott
33

Hierfür wird HttpWebRequest verwendet.

Erstellen Sie beim Postback eine HttpWebRequest an Ihren Drittanbieter und veröffentlichen Sie die Formulardaten. Sobald dies erledigt ist, können Sie Response.Redirect verwenden, wo immer Sie möchten.

Sie erhalten den zusätzlichen Vorteil, dass Sie nicht alle Serversteuerelemente benennen müssen, um das Formular von Drittanbietern zu erstellen. Sie können diese Übersetzung beim Erstellen der POST-Zeichenfolge ausführen.

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");

Wenn der Benutzer jedoch die Antwortseite dieses Formulars anzeigen muss, können Sie nur Server.Transfer verwenden. Dies funktioniert möglicherweise oder nicht.

FlySwat
quelle
ist es das, was ich verwenden werde, wenn ich ein zusätzliches Formular als das asp.net-Formular möchte. Ich muss einige Daten an eine externe URL senden, die für die sichere 3D-Zahlung bestimmt ist. Dann muss ich Informationen von der Anfrage zurückerhalten. Ist das der Weg das zu tun? Vielen Dank
Barbaros Alp
Wenn der Benutzer die Antwort sehen soll, können Sie einfach den Inhalt und die entsprechenden Header zurücksenden. Abhängig von der Verwendung der relativen Ressourcen müssen Sie möglicherweise einige Aspekte des Ergebnisses ändern, dies ist jedoch durchaus möglich.
Thomas S. Trias
6
Der Benutzer verfügt möglicherweise über Cookie-Daten, die über diese Methode nicht übertragen werden, da dies vom Server geschieht.
Scott
7

Dies sollte das Leben viel einfacher machen. Sie können die Response.RedirectWithData (...) -Methode einfach in Ihrer Webanwendung verwenden.

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
ZooZ
quelle
6

Neu in ASP.Net 3.5 ist diese "PostBackUrl" -Eigenschaft von ASP-Schaltflächen. Sie können die Adresse der Seite festlegen, auf der Sie direkt posten möchten. Wenn Sie auf diese Schaltfläche klicken, wird sie nicht wie gewohnt auf derselben Seite veröffentlicht, sondern auf der von Ihnen angegebenen Seite. Praktisch. Stellen Sie sicher, dass UseSubmitBehavior ebenfalls auf TRUE gesetzt ist.

Mike K.
quelle
4

Ich dachte, es könnte interessant sein zu teilen, dass Heroku dies mit seinem SSO für Add-On-Anbieter tut

Ein Beispiel für die Funktionsweise finden Sie in der Quelle des Tools "kensa":

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

Und kann in der Praxis gesehen werden, wenn Sie Javascript ausschalten. Beispielseitenquelle:

<!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>
Jacob
quelle
3

PostbackUrl kann auf Ihrer Asp-Schaltfläche eingestellt werden, um auf einer anderen Seite zu posten.

Wenn Sie dies in Codebehind tun müssen, versuchen Sie es mit Server.Transfer.

Jimmy
quelle
2

@Matt,

Sie können weiterhin die HttpWebRequest verwenden und dann die Antwort, die Sie erhalten, an die tatsächliche Ausgabestream-Antwort weiterleiten. Dies würde die Antwort an den Benutzer zurücksenden. Das einzige Problem ist, dass alle relativen URLs beschädigt würden.

Trotzdem kann das funktionieren.

FlySwat
quelle
2

Folgendes würde ich tun:

Fügen Sie die Daten in ein Standardformular ein (ohne das Attribut runat = "server") und legen Sie die Aktion des Formulars fest, die auf der externen Zielseite veröffentlicht werden soll. Vor dem Senden würde ich die Daten mit einer XmlHttpRequest an meinen Server senden und die Antwort analysieren. Wenn die Antwort bedeutet, dass Sie mit dem POSTing außerhalb des Standorts fortfahren sollten, würde ich (das JavaScript) mit dem Beitrag fortfahren, andernfalls würde ich zu einer Seite auf meiner Website weiterleiten

Andrei Rînea
quelle
Dies funktioniert, es ist jedoch wichtig zu beachten, dass Sie alle serverseitigen ASP.NET-Funktionen verlieren.
Senfo
2

In PHP können Sie POST-Daten mit cURL senden. Gibt es etwas Vergleichbares für .NET?

Ja, HttpWebRequest, siehe meinen Beitrag unten.

FlySwat
quelle
2

Die GET- (und HEAD-) Methode sollte niemals verwendet werden, um etwas zu tun, das Nebenwirkungen hat. Ein Nebeneffekt kann darin bestehen, den Status einer Webanwendung zu aktualisieren oder Ihre Kreditkarte zu belasten. Wenn eine Aktion Nebenwirkungen hat, sollte stattdessen eine andere Methode (POST) verwendet werden.

Ein Benutzer (oder sein Browser) sollte also nicht für etwas verantwortlich gemacht werden, das von einem GET getan wird. Wenn infolge eines GET eine schädliche oder teure Nebenwirkung auftritt, liegt dies an der Webanwendung und nicht am Benutzer. Gemäß der Spezifikation darf ein Benutzeragent einer Umleitung nicht automatisch folgen, es sei denn, es handelt sich um eine Antwort auf eine GET- oder HEAD-Anforderung.

Natürlich haben viele GET-Anforderungen einige Nebenwirkungen, selbst wenn sie nur an eine Protokolldatei angehängt werden. Wichtig ist, dass die Anwendung und nicht der Benutzer für diese Effekte verantwortlich gemacht werden.

Die relevanten Abschnitte der HTTP-Spezifikation sind 9.1.1 und 9.1.2 sowie 10.3 .

erickson
quelle
1

Ich empfehle, eine HttpWebRequest zu erstellen, um Ihren POST programmgesteuert auszuführen und dann nach dem Lesen der Antwort umzuleiten, falls zutreffend.

Ben Griswold
quelle
1

Kopierbarer Code basierend auf der Methode von Pavlo Neyman

RedirectPost (String-URL, T bodyPayload) und GetPostData () sind für diejenigen gedacht, die nur einige stark typisierte Daten auf der Quellseite speichern und auf der Zielseite wieder abrufen möchten. Die Daten müssen von NewtonSoft Json.NET serialisierbar sein und Sie müssen natürlich auf die Bibliothek verweisen.

Kopieren Sie sie einfach in Ihre Seite (n) oder besser noch in die Basisklasse für Ihre Seiten und verwenden Sie sie an einer beliebigen Stelle in Ihrer Anwendung.

Mein Herz geht an alle, die 2019 aus irgendeinem Grund noch Web Forms verwenden müssen.

        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;
        }
Zar Shardan
quelle
0

Normalerweise benötigen Sie nur einen Status zwischen diesen beiden Anforderungen. Es gibt tatsächlich eine wirklich funky Möglichkeit, dies zu tun, die nicht auf JavaScript basiert (denken Sie an <noscript />).

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

Mit diesem Cookie können Sie in der folgenden Anfrage an /redirect.html die Name = Wert-Informationen abrufen. Sie können jede Art von Information in dieser Name / Wert-Paar-Zeichenfolge speichern, bis zu 4 KB Daten (typisches Cookie-Limit). Natürlich sollten Sie dies vermeiden und stattdessen Statuscodes und Flag-Bits speichern.

Nach Erhalt dieser Anfrage antworten Sie im Gegenzug mit einer Löschanforderung für diesen Statuscode.

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

Mein HTTP ist ein bisschen verrostet. Ich habe RFC2109 und RFC2965 durchgesehen, um herauszufinden, wie zuverlässig dies wirklich ist. Am liebsten möchte ich, dass der Cookie genau einmal einen Roundtrip ausführt, aber das scheint auch bei Cookies von Drittanbietern nicht möglich zu sein Dies kann ein Problem für Sie sein, wenn Sie in eine andere Domain umziehen. Dies ist immer noch möglich, aber nicht so schmerzlos wie wenn Sie Dinge in Ihrer eigenen Domain tun.

Das Problem hierbei ist die Parallelität. Wenn ein Hauptbenutzer mehrere Registerkarten verwendet und es schafft, mehrere Anforderungen derselben Sitzung zu verschachteln (dies ist sehr unwahrscheinlich, aber nicht unmöglich), kann dies zu Inkonsistenzen in Ihrer Anwendung führen.

Dies ist die <noscript /> Methode, um HTTP-Roundtrips ohne bedeutungslose URLs und JavaScript durchzuführen

Ich stelle diesen Code als Profi des Konzepts zur Verfügung: Wenn dieser Code in einem Kontext ausgeführt wird, mit dem Sie nicht vertraut sind, können Sie meiner Meinung nach herausfinden, welcher Teil was ist.

Die Idee ist, dass Sie Relocate mit einem bestimmten Status aufrufen, wenn Sie umleiten, und die URL, die Sie verschoben haben, GetState aufruft, um die Daten abzurufen (falls vorhanden).

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;
}
John Leidegren
quelle