268

我们需要提交表单并保存一些数据,然后将用户重定向到异地页面,但是在重定向时,我们需要使用 POST 而不是 GET 来“提交”表单。

我希望有一种简单的方法可以实现这一点,但我开始认为没有。我想我现在必须创建一个简单的其他页面,只包含我想要的表单,重定向到它,填充表单变量,然后对仅调用 document.forms[0].submit( );

谁能告诉我是否有替代方案?我们可能需要在项目的后期调整它,它可能会变得有点复杂,所以如果有一个简单的我们可以做到这一点,所有非其他页面依赖,那将是非常棒的。

无论如何,感谢任何和所有的回应。

4

14 回答 14

231

这样做需要了解 HTTP 重定向的工作原理。当你使用 时,你会发送一个带有HTTP 状态码 302Response.Redirect()的响应(给发出请求的浏览器),它告诉浏览器下一步要去哪里。根据定义,浏览器将通过请求来实现,即使原始请求是.GETPOST

另一种选择是使用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 解决方案也是如此,假设它适用于您的所有用户。

希望这可以帮助!

于 2008-09-06T18:54:00.723 回答
133

您可以使用这种方法:

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。

于 2010-05-10T13:16:31.040 回答
33

HttpWebRequest 用于此。

在回发时,向您的第三方创建一个 HttpWebRequest 并发布表单数据,然后一旦完成,您就可以 Response.Redirect 任何您想要的地方。

您可以获得额外的优势,即您不必命名所有服务器控件来制作第 3 方表单,您可以在构建 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,这可能会也可能不会起作用。

于 2008-09-05T19:33:34.940 回答
7

ASP.Net 3.5 中的新功能是 ASP 按钮的“PostBackUrl”属性。您可以将其设置为要直接发布到的页面的地址,当单击该按钮时,它不会像往常一样发回同一页面,而是发布到您指定的页面。便利。确保 UseSubmitBehavior 也设置为 TRUE。

于 2010-05-13T07:15:38.987 回答
7

这应该让生活更轻松。您可以轻松地在 Web 应用程序中使用 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
于 2012-08-29T09:44:53.200 回答
5

认为分享 heroku 使用它的 SSO 到 Add-on 提供程序可能会很有趣

在“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>
于 2013-10-25T19:34:01.073 回答
3

PostbackUrl 可以在您的 asp 按钮上设置以发布到不同的页面。

如果您需要在代码隐藏中执行此操作,请尝试使用 Server.Transfer。

于 2008-09-05T19:11:43.933 回答
3

@马特,

您仍然可以使用 HttpWebRequest,然后将收到的响应定向到实际的输出流响应,这会将响应返回给用户。唯一的问题是任何相对 url 都会被破坏。

不过,这可能会奏效。

于 2008-09-05T21:15:38.470 回答
2

我建议构建一个 HttpWebRequest 以编程方式执行您的 POST,然后在读取响应后重定向(如果适用)。

于 2008-09-05T19:20:41.977 回答
2

这就是我要做的:

将数据放在标准表单中(没有 runat="server" 属性),并将表单的操作设置为发布到目标站外页面。在提交之前,我会使用 XmlHttpRequest将数据提交到我的服务器并分析响应。如果响应意味着您应该继续进行异地发布,那么我(JavaScript)将继续发布该帖子,否则我将重定向到我网站上的页面

于 2008-09-06T17:06:20.030 回答
2

在 PHP 中,您可以使用 cURL 发送 POST 数据。.NET 有可比性吗?

是的,HttpWebRequest,请参阅下面的帖子。

于 2008-09-06T21:02:26.967 回答
2

GET(和 HEAD)方法不应该被用来做任何有副作用的事情。副作用可能是更新 Web 应用程序的状态,或者可能会从您的信用卡中收取费用。如果某个动作有副作用,则应使用另一种方法 (POST)。

因此,用户(或他们的浏览器)不应为 GET 所做的事情负责。如果 GET 导致某些有害或昂贵的副作用发生,那将是 Web 应用程序的错,而不是用户的错。根据规范,用户代理不能自动跟随重定向,除非它是对 GET 或 HEAD 请求的响应。

当然,许多 GET 请求确实有一些副作用,即使它只是附加到日志文件。重要的是应用程序,而不是用户,应该对这些影响负责。

HTTP 规范的相关部分是9.1.1 和 9.1.2以及10.3

于 2008-09-08T22:02:44.107 回答
1

通常,您所需要的只是在这两个请求之间携带一些状态。实际上有一种非常时髦的方法可以做到这一点,它不依赖于 JavaScript(想想 <noscript/>)。

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

有了那个 cookie,您可以在以下对 /redirect.html 的请求中检索 name=value 信息,您可以在这个 name/value 对字符串中存储任何类型的信息,最多可以说 4K 的数据(典型的 cookie 限制)。当然,您应该避免这种情况并改为存储状态代码和标志位。

收到此请求后,您会回复该状态代码的删除请求。

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

我的 HTTP 有点生疏我一直在通过 RFC2109 和 RFC2965 来确定这到底有多可靠,最好我希望 cookie 只往返一次,但这似乎也不可能,第三方 cookie如果您要迁移到另一个域,这对您来说可能是个问题。这仍然是可能的,但不像您在自己的域中做事那样轻松。

这里的问题是并发性,如果高级用户使用多个选项卡并设法交错属于同一会话的几个请求(这不太可能,但并非不可能),这可能会导致您的应用程序出现不一致。

这是在没有无意义的 URL 和 JavaScript 的情况下进行 HTTP 往返的 <noscript/> 方式

我提供此代码作为概念教授:如果此代码在您不熟悉的上下文中运行,我认为您可以弄清楚哪个部分是什么。

这个想法是您在重定向时调用 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;
}
于 2009-08-30T11:50:35.643 回答
1

基于Pavlo Neyman 方法的可复制粘贴代码

RedirectPost(string url, T bodyPayload) 和 GetPostData() 适用于那些只想在源页面中转储一些强类型数据并将其取回目标页面的人。数据必须可由 NewtonSoft Json.NET 序列化,当然您需要引用该库。

只需将其复制粘贴到您的页面或更好的页面基类中,然后在您的应用程序中的任何位置使用它。

无论出于何种原因,在 2019 年仍需使用 Web 表单的所有人,我都向他们表示衷心的感谢。

        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;
        }
于 2019-02-28T15:56:42.917 回答