Response.Redirect用POST代替Get?


248

我们需要提交表单并保存一些数据,然后将用户重定向到非现场页面,但是在重定向中,我们需要使用POST(而不是GET)“提交”表单。

我希望有一个简单的方法来完成此任务,但我开始认为没有。我认为我现在必须创建一个简单的其他页面,仅包含我想要的表单,重定向到该页面,填充表单变量,然后对仅调用document.forms [0] .submit()的脚本执行body.onload调用);

谁能告诉我是否有其他选择?我们可能稍后在项目中需要对此进行调整,这可能会变得有些复杂,因此,如果有一个简单的方法,我们可以完成所有与其他页面无关的操作,这将是很棒的。

无论如何,感谢您的所有回复。


在PHP中,您可以使用cURL发送POST数据。.NET是否有可比的东西?
布赖恩·沃肖

我认为这是您要找的简单答案。我简直不敢相信它是多么的巧妙... stackoverflow.com/a/6062248/110549
JoeCool 2012年

@BrianWarshaw我找到System.Net.Http.HttpClient msdn.microsoft.com/en-us/library/...非常直观和快捷的使用。
Stoyan Dimov

Answers:


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

不幸的是,这并不总是可行。 不同的浏览器以不同的方式实现此功能,因为它不是通用的状态码。

las,与Opera和FireFox开发人员不同,IE开发人员从未阅读过该规范,即使最新,最安全的IE7也会将POST请求从域A重定向到域B,而不会发出任何警告或确认对话框!Safari也以一种有趣的方式运行,虽然它不引发确认对话框并执行重定向,但它丢弃了POST数据,有效地将307重定向更改为更常见的302。

因此,据我所知,实现此类功能的唯一方法是使用Javascript。我可以想到两种选择:

  1. 创建该窗体并将其action属性指向第三方服务器。然后,在“提交”按钮上添加一个click事件,该事件首先使用数据对您的服务器执行AJAX请求,然后允许将该表单提交给第三方服务器。
  2. 创建表单以发布到您的服务器。提交表单后,向用户显示一个包含表单的页面,其中包含您要传递的所有数据,所有数据均在隐藏的输入中。只需显示诸如“正在重定向...”之类的消息即可。然后,将一个javascript事件添加到将表单提交给第三方服务器的页面。

由于两个原因,我会选择第二个。首先,它比第一个更可靠,因为它不需要Javascript即可运行。对于未启用它的用户,您始终可以使隐藏表单的提交按钮可见,并指示他们在5秒钟以上的时间内按下它。其次,您可以决定将哪些数据传输到第三方服务器。如果仅使用处理表单的方式,则将传递所有发布数据,而这并非总是您想要的。假定307解决方案适用于所有用户,则与307解决方案相同。

希望这可以帮助!


1
请将“将点击事件添加到提交按钮”更改为“将提交事件添加到表单”。提交表单的方法不只一种,例如,按Enter键并专注于任何文本输入,更不用说以编程方式提交了。
temoto

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(如果可以的话,我会再加您)。这就是答案。雄辩而有重点。您甚至包括了所有代码来完成此任务,而无需费力气。我在aspx页面上的iframe中使用了它,它完美呈现了所有内容-无需重写url。优秀作品!给使用iframe的用户的提示:我将iframe指向另一个aspx页面,然后执行该代码。
MikeTeeVee 2012年

1
确实有效!我看到的唯一不好的事情是,如果您按下浏览器的后退按钮,它将再次执行请求,因此您再也无法真正到达上一页了。为此有工作环境吗?
山 Schneiders 2013年

4
支持这样做的原因是使用307。这是因为POST操作旨在成为幂等事务。这个答案仅仅是一个巧合,它可以正常工作,将来很容易被浏览器阻止。
Sam Rueby 2013年

2
@山姆好点。作为反驳:按照最常见的回答,IE开发人员甚至都没有读过307。其他阅读它的人错误地实现了它。这意味着307显然会使智能人士困惑(假设浏览器开发人员很聪明),并且容易产生解释错误。至少对于我来说,以上方法很明显,并且它适用于过去和现在的所有浏览器。当我们的开发人员为过去(阅读IE7)和今天进/出而战时,不必太担心未来。恕我直言,因为每个人都正确地得到了它,所以应该保持原样。将来阻止它的依据是什么?
so_mv 2013年

2
这与TGHW的第二种选择有何不同?您是否还潜在地要求粗心的人引入XSS漏洞?
Scott

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,这可能会或可能不会起作用。


如果我想要一个比asp.net表单更多的表单,这是我将要使用的表单。我需要将一些数据发布到用于3d安全付款的外部网址中,然后才需要从请求中返回信息。这是这样做的方式吗?谢谢
巴尔巴罗斯阿尔普

如果您需要用户查看响应,则只需发送回内容和任何适当的标题即可。您可能必须根据相对资源的使用来修改结果的某些方面,但是肯定是可能的。
Thomas S. Trias 2010年

6
用户可能具有无法通过此方法传输的cookie数据,因为这是从服务器发生的。
斯科特

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

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

可以在asp按钮上设置PostbackUrl以发布到其他页面。

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


2

@马特

您仍然可以使用HttpWebRequest,然后将收到的响应定向到实际的outputstream响应,这会将响应提供给用户。唯一的问题是任何相对URL都将被破坏。

仍然可以。


2

这是我要做的:

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


这可行,但是要注意,您松开了所有ASP.NET服务器端功能。
senfo

2

在PHP中,您可以使用cURL发送POST数据。.NET是否具有可比的功能?

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


2

绝对不要使用GET(和HEAD)方法来做任何有副作用的事情。副作用可能是正在更新Web应用程序的状态,或者可能是在向您的信用卡收费。如果操作有副作用,则应改用另一种方法(POST)。

因此,不应让用户(或其浏览器)对GET所做的事情负责。如果由于GET产生了一些有害或昂贵的副作用,那将是Web应用程序而不是用户的错误。根据规范,一个用户代理除非是对GET或HEAD请求的响应,否则不得自动遵循重定向。

当然,即使只是将其附加到日志文件中,很多GET请求也确实有一些副作用。重要的是应由应用程序(而不是用户)对这些后果负责。

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


1

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


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

0

通常,您只需要在这两个请求之间传递某种状态即可。实际上,有一种非常时髦的方法可以实现,它不依赖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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.