Response.Redirect с POST вместо Get?

У нас есть требование принять форму отправки и сохранить некоторые данные, а затем перенаправить пользователя на страницу за пределами сайта, но при перенаправлении нам нужно "отправить" форму с POST, а не GET.

Я надеялся, что есть простой способ сделать это, но я начинаю думать, что нет. Я думаю, что теперь я должен создать простую другую страницу, только с той формой, которую я хочу, перенаправляю на нее, заполнять переменные формы, а затем делать вызов body.onload на script, который просто вызывает document.forms [0]. подать();

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

В любом случае, спасибо за любые ответы.

Ответ 1

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

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

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

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

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

Ответ 2

Вы можете использовать этот aproach:

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.

Ответ 3

Для этого используется 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, и это может работать или не работать.

Ответ 4

Это должно сделать жизнь намного проще. Вы легко можете использовать метод 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

Ответ 5

Что-то новое в ASP.Net 3.5 - это свойство "PostBackUrl" кнопок ASP. Вы можете установить его на адрес страницы, на которую хотите отправить сообщение напрямую, и когда эта кнопка будет нажата, вместо того, чтобы отправлять обратно на ту же страницу, что и обычная, вместо этого отправляется на указанную страницу. Handy. Убедитесь, что для параметра UseSubmitBehavior установлено значение TRUE.

Ответ 6

PostbackUrl может быть установлен на вашей кнопке asp для публикации на другой странице.

если вам нужно сделать это в коде, попробуйте Server.Transfer.

Ответ 7

Подумал, что может быть интересно поделиться тем, что герой делает это с ним 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>

Ответ 8

@Matt,

Вы все еще можете использовать HttpWebRequest, а затем направлять ответ, который вы получаете, на фактический ответ выходного потока, это ответ будет возвращено пользователю. Единственная проблема заключается в том, что любые относительные URL-адреса будут нарушены.

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

Ответ 9

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

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

Ответ 10

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

Да, HttpWebRequest, см. мое сообщение ниже.

Ответ 11

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

Ответ 12

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

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

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

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

Ответ 13

Как правило, все, что вам понадобится, - это нести какое-то состояние между этими двумя запросами. На самом деле это действительно напуганный способ сделать это, который не полагается на JavaScript (think < noscript=).

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

С помощью этого файла cookie вы можете в следующем запросе /redirect.html получить имя = значение info, вы можете хранить любую информацию в этой строке пары имя/значение, до 4K данных (типичный файл cookie предел). Конечно, вы должны избегать этого и сохранять коды состояния и биты флага.

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

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

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

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

Это способ < 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;
}