Response.Redirect dengan POST bukannya Get?

248

Kami memiliki persyaratan untuk mengambil pengiriman formulir dan menyimpan beberapa data, kemudian mengarahkan pengguna ke halaman di luar kantor, tetapi dalam mengarahkan ulang, kita perlu "mengirimkan" formulir dengan POST, bukan GET.

Saya berharap ada cara mudah untuk mencapai ini, tetapi saya mulai berpikir tidak ada. Saya pikir saya sekarang harus membuat halaman lain yang sederhana, hanya dengan bentuk yang saya inginkan, redirect ke sana, isi variabel formulir, kemudian lakukan pemanggilan body.onload ke skrip yang hanya memanggil document.forms [0] .submit ( );

Adakah yang bisa memberi tahu saya jika ada alternatif? Kita mungkin perlu men-tweak ini nanti dalam proyek, dan mungkin agak rumit, jadi jika ada yang mudah kita bisa melakukan ini semua tergantung halaman lain yang akan fantastis.

Bagaimanapun, terima kasih atas semua dan semua tanggapan.

Matt Dawdy
sumber
Di PHP, Anda dapat mengirim data POST dengan cURL. Apakah ada sesuatu yang sebanding dengan .NET?
Brian Warshaw
Saya pikir ini adalah jawaban mudah yang Anda cari. Saya tidak percaya betapa cerdiknya ... stackoverflow.com/a/6062248/110549
JoeCool
@BrianWarshaw Saya menemukan System.Net.Http.HttpClient msdn.microsoft.com/en-us/library/… sangat intuitif dan cepat digunakan.
Stoyan Dimov

Jawaban:

228

Melakukan hal ini membutuhkan pemahaman tentang cara pengalihan HTTP bekerja. Saat Anda menggunakan Response.Redirect(), Anda mengirim respons (ke browser yang mengajukan permintaan) dengan HTTP Status Code 302 , yang memberi tahu browser tempat yang harus dikunjungi selanjutnya. Menurut definisi, browser akan membuatnya melalui GETpermintaan, bahkan jika permintaan asli adalah aPOST .

Pilihan lain adalah menggunakan HTTP Status Code 307 , yang menentukan bahwa browser harus membuat permintaan pengalihan dengan cara yang sama seperti permintaan asli, tetapi untuk meminta pengguna dengan peringatan keamanan. Untuk melakukan itu, Anda akan menulis sesuatu seperti ini:

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

Sayangnya, ini tidak akan selalu berhasil. Browser yang berbeda menerapkan ini secara berbeda , karena ini bukan kode status yang umum.

Sayangnya, tidak seperti pengembang Opera dan FireFox, pengembang IE tidak pernah membaca spesifikasi, dan bahkan IE7 terbaru, paling aman akan mengarahkan permintaan POST dari domain A ke domain B tanpa peringatan atau dialog konfirmasi! Safari juga bertindak dengan cara yang menarik, walaupun tidak memunculkan dialog konfirmasi dan melakukan pengalihan, ia membuang data POST, secara efektif mengubah 307 pengalihan menjadi 302 yang lebih umum.

Jadi, sejauh yang saya tahu, satu-satunya cara untuk mengimplementasikan sesuatu seperti ini adalah dengan menggunakan Javascript. Ada dua opsi yang bisa saya pikirkan di atas kepala saya:

  1. Buat formulir dan miliki action titik atributnya ke server pihak ketiga. Kemudian, tambahkan acara klik ke tombol kirim yang pertama kali mengeksekusi permintaan AJAX ke server Anda dengan data, dan kemudian memungkinkan formulir untuk dikirimkan ke server pihak ketiga.
  2. Buat formulir untuk dikirim ke server Anda. Saat formulir dikirimkan, tunjukkan pengguna halaman yang memiliki formulir di dalamnya dengan semua data yang ingin Anda sampaikan, semua dalam input tersembunyi. Cukup tampilkan pesan seperti "Redirecting ...". Kemudian, tambahkan acara javascript ke halaman yang mengirimkan formulir ke server pihak ketiga.

Dari keduanya, saya akan memilih yang kedua, karena dua alasan. Pertama, ini lebih dapat diandalkan daripada yang pertama karena Javascript tidak diperlukan untuk itu berfungsi; bagi mereka yang tidak mengaktifkannya, Anda selalu dapat membuat tombol kirim untuk formulir tersembunyi terlihat, dan memerintahkan mereka untuk menekannya jika perlu lebih dari 5 detik. Kedua, Anda dapat memutuskan data apa yang dikirimkan ke server pihak ketiga; jika Anda hanya menggunakan formulir saat berjalan, Anda akan melewati semua data pos, yang tidak selalu seperti yang Anda inginkan. Sama untuk solusi 307, dengan asumsi itu bekerja untuk semua pengguna Anda.

Semoga ini membantu!

tghw
sumber
1
Silakan, ubah "tambahkan acara klik ke tombol kirim" ke "tambahkan acara kirim ke formulir". Ada lebih dari satu metode untuk mengirimkan formulir, misalnya menekan Enter dengan input teks apa pun yang terfokus, apalagi mengirimkan terprogram.
temoto
122

Anda dapat menggunakan pendekatan ini:

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

Sebagai hasil tepat setelah klien akan mendapatkan semua html dari server, acara onload terjadi yang memicu formulir kirim dan kirim semua data ke postbackUrl yang ditentukan.

Pavlo Neiman
sumber
9
+1 (saya akan menambahkan Anda lebih banyak jika saya bisa). Inilah jawabannya. Fasih dan to the point. Anda bahkan memasukkan semua kode untuk melakukannya alih-alih berbicara terlalu keras. Saya menggunakan ini di iframe di halaman aspx saya dan membuat semuanya dengan sempurna - tanpa url penulisan ulang. Kerja bagus! TIP untuk mereka yang menggunakan iframe: Saya mengarahkan iframe saya ke halaman aspx lain yang kemudian mengeksekusi kode ini.
MikeTeeVee
1
Itu berhasil! Satu-satunya hal buruk yang saya lihat adalah jika Anda menekan tombol kembali browser, itu melakukan permintaan lagi, sehingga Anda tidak dapat benar-benar mencapai halaman sebelumnya lagi. Apakah ada solusi untuk itu?
Mt. Schneider
4
Ada alasan mengapa metode yang didukung untuk melakukan ini menggunakan 307. Tindakan POST dimaksudkan untuk transaksi idempoten. Jawaban ini hanyalah peretasan yang berhasil dan dapat dengan mudah diblokir oleh browser di masa mendatang.
Sam Rueby
2
@sam poin bagus. Sebagai argumen balasan: sesuai jawaban teratas pengembang IE bahkan tidak membaca sekitar 307. Orang lain yang membacanya menerapkannya dengan salah. Itu berarti 307 jelas membingungkan bagi para pintar (dengan asumsi pengembang browser cerdas) dan rentan terhadap kesalahan interpretasi. Pendekatan di atas jelas, setidaknya bagi saya dan bekerja di semua browser dulu dan sekarang. Tidak terlalu khawatir tentang masa depan karena kami para pengembang memperjuangkan masa lalu (baca IE7) dan hari ini masuk / keluar. IMHO, karena semua orang mendapatkannya dengan benar, mereka harus menyimpannya apa adanya. Apa yang akan menjadi alasan untuk memblokirnya, di masa depan?
so_mv
2
Bagaimana ini tidak efektif sama dengan opsi kedua oleh TGHW? Bukankah Anda juga berpotensi meminta seseorang yang ceroboh untuk memperkenalkan kerentanan XSS?
Scott
33

HttpWebRequest digunakan untuk ini.

Pada postback, buat HttpWebRequest ke pihak ketiga Anda dan posting formulir data, maka setelah selesai, Anda bisa Response.Redirect di mana pun Anda inginkan.

Anda mendapatkan keuntungan tambahan bahwa Anda tidak harus memberi nama semua kontrol server Anda untuk membuat formulir pihak ke-3, Anda dapat melakukan terjemahan ini saat membuat string 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");

Namun, jika Anda memerlukan pengguna untuk melihat halaman respons dari formulir ini, satu-satunya pilihan Anda adalah menggunakan Server.Transfer, dan itu mungkin atau mungkin tidak berfungsi.

FlySwat
sumber
apakah itu yang akan saya gunakan jika saya ingin formulir tambahan dari formulir asp.net. Saya perlu mengirim beberapa data ke url eksternal yang untuk pembayaran aman 3d, maka saya perlu mendapatkan info yang dikembalikan dari permintaan. Apakah ini cara melakukan ini? Terima kasih
Barbaros Alp
Jika Anda membutuhkan pengguna untuk melihat responsnya, Anda bisa mengirimkan kembali konten dan tajuk yang sesuai. Anda mungkin harus memodifikasi beberapa aspek hasil tergantung pada penggunaan sumber daya relatif, tetapi itu tentu saja mungkin.
Thomas S. Trias
6
Pengguna mungkin memiliki data cookie yang tidak akan dikirim melalui metode ini, karena ini terjadi dari server.
Scott
7

Ini seharusnya membuat hidup lebih mudah. Anda cukup menggunakan metode Response.RedirectWithData (...) di aplikasi web Anda dengan mudah.

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
sumber
6

Sesuatu yang baru di ASP.Net 3.5 adalah properti "PostBackUrl" ini dari tombol ASP. Anda dapat mengaturnya ke alamat halaman yang ingin Anda kirimi secara langsung, dan ketika tombol itu diklik, alih-alih mengirim kembali ke halaman yang sama seperti biasa, itu malah memposting ke halaman yang telah Anda indikasikan. Berguna. Pastikan UseSubmitBehavior juga disetel ke TRUE.

Mike K.
sumber
4

Mungkin menarik untuk berbagi bahwa heroku melakukan ini dengan SSO untuk penyedia Add-on-nya

Contoh cara kerjanya dapat dilihat pada sumber ke alat "kensa":

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

Dan bisa dilihat dalam prakteknya jika Anda mengaktifkan javascript. Sumber halaman contoh:

<!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>
Jacobs
sumber
3

PostbackUrl dapat diatur pada tombol asp Anda untuk mengirim ke halaman lain.

jika Anda perlu melakukannya di codebehind, coba Server.Transfer.

Jimmy
sumber
2

@ Mat,

Anda masih dapat menggunakan HttpWebRequest, lalu mengarahkan respons yang Anda terima ke respons outputstream aktual, ini akan melayani respons kembali ke pengguna. Satu-satunya masalah adalah bahwa setiap url relatif akan rusak.

Namun, itu mungkin berhasil.

FlySwat
sumber
2

Inilah yang akan saya lakukan:

Letakkan data dalam bentuk standar (tanpa atribut runat = "server") dan atur aksi formulir untuk dikirim ke halaman target di luar situs. Sebelum mengirimkan saya akan mengirimkan data ke server saya menggunakan XmlHttpRequest dan menganalisis responsnya. Jika jawabannya berarti Anda harus melanjutkan POSTING di luar kantor, maka saya (JavaScript) akan melanjutkan dengan posting tersebut jika tidak saya akan mengarahkan ulang ke halaman di situs saya.

Andrei Rînea
sumber
Ini berfungsi, tetapi penting untuk dicatat bahwa Anda kehilangan semua fungsionalitas sisi server ASP.NET.
senfo
2

Di PHP, Anda dapat mengirim data POST dengan cURL. Apakah ada sesuatu yang sebanding dengan .NET?

Ya, HttpWebRequest, lihat posting saya di bawah ini.

FlySwat
sumber
2

Metode GET (dan HEAD) tidak boleh digunakan untuk melakukan apa pun yang memiliki efek samping. Efek samping mungkin memperbarui status aplikasi web, atau mungkin mengisi daya kartu kredit Anda. Jika suatu tindakan memiliki efek samping, metode lain (POST) harus digunakan sebagai gantinya.

Jadi, pengguna (atau browser mereka) tidak seharusnya dimintai pertanggungjawaban atas sesuatu yang dilakukan oleh GET. Jika beberapa efek samping yang berbahaya atau mahal terjadi sebagai akibat dari GET, itu akan menjadi kesalahan aplikasi web, bukan pengguna. Menurut spesifikasi, agen pengguna tidak boleh secara otomatis mengikuti arahan ulang kecuali itu adalah respons terhadap permintaan GET atau HEAD.

Tentu saja, banyak permintaan GET memiliki beberapa efek samping, bahkan jika itu hanya ditambahkan ke file log. Yang penting adalah bahwa aplikasi, bukan pengguna, harus bertanggung jawab atas efek tersebut.

Bagian yang relevan dari spesifikasi HTTP adalah 9.1.1 dan 9.1.2 , dan 10.3 .

erickson
sumber
1

Saya sarankan membangun HttpWebRequest untuk menjalankan POST Anda secara terprogram dan kemudian mengarahkan kembali setelah membaca Respons jika berlaku.

Ben Griswold
sumber
1

Kode copy-paste berdasarkan metode Pavlo Neyman

RedirectPost (url string, T bodyPayload) dan GetPostData () adalah bagi mereka yang hanya ingin membuang beberapa data yang sangat diketik di halaman sumber dan mengambilnya kembali di target. Data harus serialisasi oleh NewtonSoft Json.NET dan Anda perlu referensi perpustakaan tentu saja.

Cukup salin-tempel ke halaman Anda atau kelas dasar yang lebih baik untuk halaman Anda dan gunakan di mana saja di aplikasi Anda.

Hati saya keluar untuk Anda semua yang masih harus menggunakan Formulir Web pada tahun 2019 untuk alasan apa pun.

        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
sumber
0

Biasanya, yang Anda perlukan hanyalah membawa beberapa kondisi di antara kedua permintaan ini. Sebenarnya ada cara yang sangat funky untuk melakukan ini yang tidak bergantung pada JavaScript (pikirkan <noscript />).

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

Dengan cookie itu di sana, Anda dapat dalam permintaan berikut untuk /redirect.html mengambil nama = info nilai, Anda dapat menyimpan segala jenis informasi dalam string pasangan nama / nilai ini, hingga 4K data (batas cookie tertentu). Tentu saja Anda harus menghindari ini dan menyimpan kode status dan bit bendera sebagai gantinya.

Setelah menerima permintaan ini, Anda membalasnya dengan permintaan penghapusan untuk kode status itu.

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

HTTP saya agak berkarat. Saya telah menggunakan RFC2109 dan RFC2965 untuk mengetahui seberapa andal ini sebenarnya, sebaiknya saya ingin cookie untuk pulang pergi sekali saja, tetapi itu sepertinya tidak mungkin, juga, cookie pihak ketiga mungkin menjadi masalah bagi Anda jika Anda pindah ke domain lain. Ini masih mungkin tetapi tidak sesakit ketika Anda melakukan hal-hal dalam domain Anda sendiri.

Masalahnya di sini adalah konkurensi, jika pengguna yang kuat menggunakan banyak tab dan mengelola untuk menyisipkan beberapa permintaan yang termasuk dalam sesi yang sama (ini sangat tidak mungkin, tetapi bukan tidak mungkin) ini dapat menyebabkan inkonsistensi dalam aplikasi Anda.

Ini adalah <noscript /> cara melakukan round trip HTTP tanpa URL dan JavaScript yang tidak berarti

Saya memberikan kode ini sebagai konsep yang luar biasa: Jika kode ini dijalankan dalam konteks yang tidak Anda kenal, saya pikir Anda dapat menentukan bagian apa yang dimaksud.

Idenya adalah bahwa Anda memanggil Relokasi dengan beberapa kondisi saat Anda mengarahkan ulang, dan URL yang Anda relokasi memanggil GetState untuk mendapatkan data (jika ada).

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
sumber