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 Status Code 302 , который сообщает браузеру, куда идти дальше. По определению браузер сделает это с помощью GETзапроса, даже если исходный запрос был POST.

Другим вариантом является использование HTTP Status Code 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-запрос из домена A в домен B без каких-либо предупреждений или диалогов подтверждения! Safari также действует интересным образом, хотя он не вызывает диалоговое окно подтверждения и выполняет перенаправление, он выбрасывает данные POST, эффективно изменяя перенаправление 307 в более распространенное 302.

Итак, насколько я знаю, единственный способ реализовать что-то подобное - это использовать Javascript. Есть два варианта, которые я могу придумать вне головы:

  1. Создайте форму и actionукажите ее атрибут на стороннем сервере. Затем добавьте событие click к кнопке отправки, которая сначала выполняет запрос AJAX на ваш сервер с данными, а затем разрешает отправку формы на сторонний сервер.
  2. Создайте форму для публикации на вашем сервере. Когда форма отправлена, покажите пользователю страницу, на которой есть форма со всеми данными, которые вы хотите передать, все в скрытом виде. Просто покажите сообщение типа «Перенаправление ...». Затем добавьте событие javascript на страницу, которая отправляет форму на сторонний сервер.

Из двух я бы выбрал второе по двум причинам. Во-первых, он более надежен, чем первый, потому что для его работы не требуется Javascript; для тех, у кого она не включена, вы всегда можете сделать видимой кнопку отправки для скрытой формы и дать им команду нажать ее, если это займет более 5 секунд. Во-вторых, вы можете решить, какие данные будут передаваться на сторонний сервер; если вы просто обрабатываете форму по мере ее поступления, вы будете передавать все почтовые данные, что не всегда то, что вы хотите. То же самое для решения 307, при условии, что оно работает для всех ваших пользователей.

Надеюсь это поможет!

tghw
источник
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 с сервера, происходит событие загрузки, которое запускает отправку и отправку всех данных в определенный postbackUrl.

Павло Нейман
источник
9
+1 (я бы добавил тебя больше, если бы мог). Это ответ. Красноречивый и по существу. Вы даже включили весь код, чтобы сделать это, вместо того, чтобы говорить с головы до головы. Я использовал это в iframe на моей странице aspx, и он отрисовал все отлично - никаких переписывающих URL. Превосходная работа! СОВЕТ для тех, кто использует iframe: я указываю свой iframe на другую страницу aspx, которая затем выполняет этот код.
MikeTeeVee
1
Это работает! Единственное плохое, что я вижу, это то, что если вы нажмете кнопку «Назад» браузера, он снова выполнит запрос, так что вы больше не сможете перейти на предыдущую страницу. Есть ли обходной путь для этого?
гора Шнайдерс
4
Есть причина, по которой поддерживаемый метод для этого использует 307. Действия POST предназначены для идемпотентных транзакций. Этот ответ - просто взлом, который срабатывает и может быть очень легко заблокирован браузерами в будущем.
Сэм Рюби
2
@ Сам хороший момент. В качестве контраргумента: согласно верхнему ответу разработчик IE даже не читал о 307. Другие, кто читал его, реализовали это неправильно. Это означает, что 307 явно сбивает с толку смартов (при условии, что разработчики браузеров умны) и склонен к ошибкам интерпретации. Вышеуказанный подход понятен, по крайней мере для меня, и он работает во всех браузерах прошлого и настоящего. Не слишком беспокоитесь о будущем, так как мы, разработчики, боремся с прошлым (читайте IE7) и сегодняшним днем. ИМХО, так как все правильно поняли, они должны оставить все как есть. Каково было бы обоснование, чтобы заблокировать это в будущем?
so_mv
2
Как это не так же, как второй вариант TGHW? Разве вы также не просите кого-нибудь небрежно представить уязвимость XSS?
Скотт
33

HttpWebRequest используется для этого.

При обратной передаче создайте запрос HttpWebRequest для третьей стороны и опубликуйте данные формы, а затем, как только это будет сделано, вы можете Response.Redirect куда угодно.

Вы получаете дополнительное преимущество, заключающееся в том, что вам не нужно называть все свои элементы управления сервером для создания сторонней формы, вы можете выполнить этот перевод при построении строки 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, и это может работать, а может и не работать.

FlySwat
источник
это то, что я собираюсь использовать, если я хочу дополнительную форму, чем форма 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
ZOOZ
источник
6

Что-то новое в ASP.Net 3.5 - это свойство «PostBackUrl» кнопок ASP. Вы можете установить для него адрес страницы, на которую вы хотите отправить сообщение, и когда вы нажимаете эту кнопку, вместо того, чтобы отправлять обратно на ту же страницу, как обычно, она вместо этого отправляет на указанную вами страницу. Handy. Убедитесь, что 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, а затем направить полученный ответ на фактический ответ outputtream, который будет возвращать ответ пользователю. Единственная проблема заключается в том, что любые относительные URL будут нарушены.

Тем не менее, это может сработать.

FlySwat
источник
2

Вот что я бы сделал:

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

Андрей Ринея
источник
Это работает, но важно отметить, что вы теряете все функции сервера ASP.NET.
Senfo
2

В PHP вы можете отправлять данные POST с помощью cURL. Есть что-то сопоставимое для .NET?

Да, HttpWebRequest, см. Мой пост ниже.

FlySwat
источник
2

Метод GET (и HEAD) никогда не должен использоваться для выполнения каких-либо побочных эффектов. Побочным эффектом может быть обновление состояния веб-приложения или списание средств с вашей кредитной карты. Если у действия есть побочные эффекты, следует использовать другой метод (POST).

Таким образом, пользователь (или его браузер) не должен нести ответственность за то, что сделал GET. Если какой-либо вредный или дорогой побочный эффект возник в результате GET, то это будет вина веб-приложения, а не пользователя. Согласно спецификации, пользовательский агент не должен автоматически следовать перенаправлению, если он не является ответом на запрос GET или HEAD.

Конечно, многие запросы GET имеют некоторые побочные эффекты, даже если они просто добавляются в файл журнала. Важно то, что приложение, а не пользователь, должно нести ответственность за эти эффекты.

Соответствующими разделами спецификации HTTP являются 9.1.1 и 9.1.2 и 10.3 .

Эриксон
источник
1

Я предлагаю создать HttpWebRequest для программного выполнения вашего POST, а затем перенаправить после прочтения Ответа, если это применимо.

Бен Грисволд
источник
1

Копируемый код, основанный на методе Павла Неймана

RedirectPost (string 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 получить информацию об имени = значении, вы можете сохранить любую информацию в этой строке пары имя / значение, вплоть до 4 КБ данных (типичное ограничение для файлов cookie). Конечно, вы должны избегать этого и хранить вместо этого коды состояния и биты флага.

Получив этот запрос, вы в ответ отвечаете запросом на удаление этого кода состояния.

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

Мой HTTP немного ржавый. Я прошёл через RFC2109 и RFC2965, чтобы выяснить, насколько это действительно надежно, желательно, чтобы cookie отправлялся в оба конца ровно один раз, но это не представляется возможным, в том числе и сторонние файлы cookie. может быть проблемой для вас, если вы переезжаете в другой домен. Это все еще возможно, но не так безболезненно, как когда вы делаете что-то внутри своего домена.

Проблема здесь заключается в параллелизме, если опытный пользователь использует несколько вкладок и ему удается чередовать пару запросов, принадлежащих одному и тому же сеансу (это очень маловероятно, но не невозможно), это может привести к несоответствиям в вашем приложении.

Это способ <noscript /> выполнять обходы HTTP без бессмысленных URL и JavaScript

Я предоставляю этот код как основную идею: если этот код выполняется в контексте, с которым вы не знакомы, я думаю, вы сможете решить, что это за часть.

Идея заключается в том, что при перенаправлении вы вызываете Relocate с некоторым состоянием, а 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;
}
Джон Лейдегрен
источник