Response.Redirect з POST замість Get?


248

У нас є вимога прийняти форму та зберегти деякі дані, а потім перенаправити користувача на сторінку в сторону сайту, але при переадресації нам потрібно "подати" форму з POST, а не GET.

Я сподівався, що існує простий спосіб досягти цього, але я починаю вважати, що цього немає. Я думаю, що зараз я повинен створити просту іншу сторінку, просто з потрібною формою, перенаправити на неї, заповнити змінні форми, потім зробити виклик body.onload до сценарію, який просто викликає document.forms [0] .submit ( );

Хтось може мені сказати, чи є альтернатива? Нам, можливо, доведеться виправити це пізніше в проекті, і це може скластися складніше, так що якби було легке, ми могли б зробити це все незалежно від інших сторінок, що було б фантастично.

У будь-якому випадку, дякую за будь-які відповіді.


У PHP можна надсилати дані POST за допомогою CURL. Чи є щось порівнянне для .NET?
Брайан Варшо

Я думаю, що це проста відповідь, яку ви шукали. Я не міг повірити , як геніально це ... stackoverflow.com/a/6062248/110549
JoeCool

@BrianWarshaw Я вважаю System.Net.Http.HttpClient msdn.microsoft.com/en-us/library/… дуже інтуїтивно зрозумілим та швидким у використанні.
Стоян Дімов

Відповіді:


228

Для цього потрібно зрозуміти, як працює переадресація HTTP. Під час використання Response.Redirect()ви надсилаєте відповідь (браузеру, який подав запит) за допомогою коду статусу HTTP 302 , який повідомляє браузеру, куди йти далі. За визначенням, браузер зробить це через GETзапит, навіть якщо початковий запит був POST.

Іншим варіантом є використання коду статусу HTTP 307 , який вказує, що браузер повинен робити запит на переадресацію так само, як і вихідний запит, але сповіщати користувача про попередження безпеки. Для цього ви б написали щось подібне:

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

На жаль, це не завжди спрацює. Різні браузери реалізують це по-різному , оскільки це не є загальним кодом статусу.

На жаль, на відміну від розробників Opera та FireFox, розробники IE ніколи не читали специфікацію, і навіть останній, найбезпечніший IE7 перенаправляє запит POST з домену А на домен B без будь-яких діалогових попереджень і підтверджень! Safari також діє цікаво, хоча він не піднімає діалог підтвердження і не виконує переадресацію, він викидає дані POST, фактично змінюючи перенаправлення 307 на більш поширене 302.

Отже, наскільки я знаю, єдиним способом реалізувати щось подібне було б використовувати Javascript. Є два варіанти, які я можу придумати вгорі голови:

  1. Створіть форму та actionвкажіть її атрибут на сторонній сервер. Потім додайте подію клацання до кнопки подання, яка спочатку виконує запит AJAX на ваш сервер із даними, а потім дозволяє подати форму на сторонній сервер.
  2. Створіть форму для публікації на своєму сервері. Після подання форми покажіть користувачеві сторінку, на якій є форма, з усіма даними, які ви хочете передати, усі у прихованих введеннях. Просто покажіть повідомлення типу "Перенаправлення ...". Потім додайте подію javascript на сторінку, яка подає форму на сторонній сервер.

З двох я вибрав би другу, з двох причин. По-перше, він надійніший за перший, оскільки для його роботи Javascript не потрібен; для тих, хто не ввімкнув цю функцію, ви завжди можете зробити видимою кнопку для надсилання прихованої форми і доручити натиснути її, якщо це займе більше 5 секунд. По-друге, ви можете вирішити, які дані передаються на сторонній сервер; якщо ви просто використовуєте форму, як вона проходить, ви будете переносити всі дані публікації, що не завжди є тим, що ви хочете. Те саме для рішення 307, якщо припустити, що він працював для всіх ваших користувачів.

Сподіваюсь, це допомагає!


1
Змініть "додати подію натискання на кнопку подання", щоб "додати подію подання до форми". Існує більше одного способу подання форми, наприклад, натискання клавіші Enter з будь-яким фокусом введення тексту, не кажучи вже про програмне надсилання.
темто

122

Ви можете використовувати цей підхід:

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

В результаті відразу після клієнта отримає все HTML від сервера подія OnLoad мати місце , що спускові підпорядковану форму і розміщувати всі дані в певну PostBackUrl.


9
+1 (я б тобі ще плюс, якщо міг). Це - відповідь. Красномовно і до речі. Ви навіть включили весь код, щоб це зробити, замість того, щоб говорити вгорі голови. Я використав це в iframe на своїй сторінці aspx, і це все вийшло ідеально - жодних URL-адрес переписування. Відмінна робота! Порада для тих, хто використовує iframe: я вказую свій iframe на іншу сторінку aspx, яка потім виконує цей код.
MikeTeeVee

1
Це працює! Єдине погане, що я бачу, це те, що якщо натиснути кнопку повернення браузера, він знову робить запит, тому ви вже не можете перейти на попередню сторінку. Чи є для цього робоче коло?
Mt. Шнайдер

4
Існує причина, що підтримуваний метод для цього - використання 307. Дії POST призначені для безвідмовних транзакцій. Ця відповідь - це просто хакер, який, можливо, спрацює і може бути дуже легко заблокований браузерами в майбутньому.
Сем Рюбі

2
@sam хороший пункт. Як протилежний аргумент: відповідно до верхньої відповіді, розробник IE навіть не читав про 307. Інші, хто читав це, реалізували це неправильно. Це означає, що 307 явно плутає розум (припускаючи, що розробники браузерів розумні) та схильні до помилок інтерпретації. Вище підхід зрозумілий, принаймні для мене, і він працює у всіх браузерах минулого та сучасного. Не надто переживайте за майбутнє, оскільки ми розробники бореться за минуле (читайте IE7) та сьогоднішній день в / з ІМХО, оскільки всі отримали його правильно, вони повинні зберігати його як є. Що було б обґрунтуванням блокувати це в майбутньому?
so_mv

2
Як це ефективно не те саме, що другий варіант TGHW? Чи ви також потенційно не просите когось необережного ввести вразливість XSS?
Скотт

33

Для цього використовується HttpWebRequest.

Після повернення створіть HttpWebRequest до своєї третьої сторони та опублікуйте дані форми, після цього, як тільки це буде зроблено, ви зможете відповісти. Перенаправити куди завгодно.

Ви отримуєте додаткову перевагу, що вам не потрібно називати всі серверні елементи управління, щоб створити форму сторонніх сторін, ви можете зробити цей переклад під час створення рядка 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");

Однак якщо вам потрібен користувач, щоб переглянути сторінку відповідей з цієї форми, ваш єдиний варіант - використовувати Server.Transfer, і це може чи не може працювати.


це те, що я збираюся використовувати, якщо хочу отримати додаткову форму, ніж форму asp.net. Мені потрібно опублікувати деякі дані на зовнішній URL, який призначений для 3d-безпечного платежу, тоді мені потрібно отримати інформацію, повернуту із запиту. Це спосіб цього зробити? Дякую
Барбарос Альп

Якщо вам потрібен користувач, щоб побачити відповідь, ви можете просто надіслати назад вміст та будь-які відповідні заголовки. Можливо, вам доведеться змінювати деякі аспекти результату залежно від використання відносних ресурсів, але це, безумовно, можливо.
Томас С. Тріас

6
У користувача можуть бути дані файлів cookie, які не будуть передані цим методом, оскільки це відбувається з сервера.
Скотт

7

Це має значно полегшити життя. Ви можете просто використовувати метод Response.RedirectWithData (...) у вашому веб-додатку легко.

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

Щось нове в ASP.Net 3.5 - це властивість кнопок ASP "PostBackUrl". Ви можете встановити його за адресою сторінки, на яку ви хочете опублікувати безпосередньо, і коли натиснути цю кнопку, замість того, щоб опублікувати назад на тій самій сторінці, що і звичайна, вона замість публікації на вказаній вами сторінці. Зручно. Переконайтесь, що для UseSubmitBehavior також встановлено значення TRUE.


4

Думав, що може бути цікавим поділитися тим, що heroku робить це з допомогою SSO для постачальників додатків

Приклад того, як це працює, можна побачити у джерелі до інструменту "kensa":

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

І це можна побачити на практиці, якщо увімкнути javascript. Приклад джерела сторінки:

<!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 можна встановити на вашій кнопці asp для публікації на іншій сторінці.

якщо вам потрібно зробити це в коді позаду, спробуйте Server.Transfer.


2

@Matt,

Ви все ще можете скористатися HttpWebRequest, а потім направити отриману відповідь на фактичну відповідь вихідного потоку, це відповість на відповідь користувачеві. Єдине питання полягає в тому, що будь-які відносні URL-адреси будуть порушені.

Все-таки це може спрацювати.


2

Ось що б я зробив:

Помістіть дані у стандартну форму (без атрибута runat = "server") та встановіть дію форми для розміщення на цільовій сторінці поза сайтом. Перш ніж надсилати, я би надсилав дані на свій сервер за допомогою XmlHttpRequest та аналізував відповідь. Якщо відповідь означає, що ви повинні продовжувати відпрацьовану пошту в іншому місці, то я (JavaScript) продовжуватиму публікацію, інакше я перенаправляюсь на сторінку свого сайту


Це працює, але важливо зауважити, що ви втрачаєте всю функціональність на сервері ASP.NET.
senfo

2

У PHP можна надсилати дані POST за допомогою CURL. Чи є щось порівнянне для .NET?

Так, HttpWebRequest, дивіться моє повідомлення нижче.


2

Метод GET (і HEAD) ніколи не слід застосовувати, щоб робити щось, що має побічні ефекти. Побічним ефектом може бути оновлення стану веб-програми або може стягнути плату з вашої кредитної картки. Якщо дія має побічні ефекти, замість цього слід використовувати інший метод (POST).

Таким чином, користувач (або його браузер) не повинні нести відповідальність за те, що зроблено GET. Якщо якісь шкідливі чи дорогі побічні ефекти виникли в результаті отримання GET, це буде виною веб-програми, а не користувача. Відповідно до специфікації, агент користувача не повинен автоматично слідувати за перенаправленням, якщо це не відповідь на запит GET або HEAD.

Звичайно, багато запитів GET мають деякі побічні ефекти, навіть якщо це лише додавання до файлу журналу. Важливим є те, що за ці ефекти несе відповідальність додаток, а не користувач.

Відповідні розділи специфікації HTTP складають 9.1.1 та 9.1.2 та 10.3 .


1

Я пропоную створити HttpWebRequest для програмного виконання вашого POST та переадресації, прочитавши відповідь, якщо це застосовно.


1

Копіювальний код на основі методі Павла Неймана

RedirectPost (URL-адреса рядка, T bodyPayload) та GetPostData () призначені для тих, хто просто хоче скинути деякі сильно набрані дані на сторінку джерела та повернути їх у цільову. Дані повинні бути серіалізовані NewtonSoft Json.NET, і вам, звичайно, потрібно посилатися на бібліотеку.

Просто скопіюйте та вставте на свою сторінку (-и) або ще краще базовий клас для своїх сторінок і використовуйте його в будь-якому місці вашої програми.

Моє серце висловлюється всім вам, хто з будь-якої причини все ще повинен користуватися веб-формами у 2019 році.

        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

Як правило, все, що вам коли-небудь знадобиться, - це перенести деякий стан між цими двома запитами. Насправді це дійсно прикольний спосіб зробити це, який не покладається на JavaScript (подумайте <noscript />).

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

За допомогою цього файлу cookie ви можете в наступному запиті /redirect.html отримати ім’я = інформація про значення, ви можете зберігати будь-яку інформацію в рядку цієї пари / значення, до 4K даних (типовий ліміт файлу cookie). Звичайно, ви повинні уникати цього і зберігати коди статусу та біти прапорців.

Отримавши цей запит, у відповідь ви отримаєте запит на видалення цього коду статусу.

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

Мій HTTP трохи іржавий. Я переходив через RFC2109 і RFC2965, щоб зрозуміти, наскільки це насправді, бажано, я хотів би, щоб печиво було точно в один раз, але це, здається, не можливо, також сторонні файли cookie може стати для вас проблемою, якщо ви переїжджаєте на інший домен. Це все ще можливо, але не так безболісно, ​​як коли ви робите речі у власному домені.

Проблема тут полягає в одночасності, якщо користувач живлення використовує кілька вкладок і вдається переплести пару запитів, що належать до одного сеансу (це дуже малоймовірно, але не неможливо), це може призвести до невідповідностей у вашій програмі.

Це <noscript /> спосіб здійснювати обхід HTTP безглуздими URL-адресами та JavaScript

Я надаю цей код як професор концепції: Якщо цей код працює у контексті, який вам не знайомий, я думаю, ви можете розробити, яка частина - що.

Ідея полягає в тому, щоб ви передзвонили переїхати з деяким станом при переадресації, а URL-адреса, яку ви перемістили, дзвонить GetState, щоб отримати дані (якщо такі є).

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.