Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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.

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

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

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

Думаю, было бы интересно рассказать, что Heroku делает это с помощью единого входа для поставщиков надстроек.

Пример того, как это работает, можно увидеть в исходном коде инструмента «kensa»:

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

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

<!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>

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

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

@Мэтт,

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

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

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

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

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

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

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

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

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

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

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

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

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

Обычно все, что вам нужно, — это перенести некоторое состояние между этими двумя запросами.На самом деле есть очень необычный способ сделать это, не полагающийся на 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;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top