【问题标题】:Response.Redirect with POST instead of Get?Response.Redirect 使用 POST 而不是 Get?
【发布时间】:2010-09-07 23:30:00
【问题描述】:

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

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

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

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

【问题讨论】:

标签: asp.net https response.redirect


【解决方案1】:

基于Pavlo Neyman's method的可复制粘贴代码

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

【讨论】:

  • 有人用过这个代码吗?我发现 Response.End() 停止执行调用方法。真的希望我可以让它工作,因为它是解决我的问题的一个优雅的解决方案。
  • @StevePaul 这是设计使然。为此,它要求不向输出流写入任何其他内容。也许将其作为调用方法的最后一步调用?
【解决方案2】:

这样做需要了解 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");
}

很遗憾,这并不总是有效。 Different browsers implement this differently,因为它不是常见的状态码。

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

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

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

在这两个中,我会选择第二个,原因有两个。首先,它比第一个更可靠,因为它不需要 Javascript 即可工作;对于那些没有启用它的人,您可以随时使隐藏表单的提交按钮可见,并指示他们在超过 5 秒时按下它。其次,您可以决定将哪些数据传输到第三方服务器;如果您只使用处理经过的表单,您将传递所有发布数据,这并不总是您想要的。 307 解决方案也是如此,假设它适用于您的所有用户。

希望这会有所帮助!

【讨论】:

  • 请将“向提交按钮添加点击事件”更改为“向表单添加提交事件”。提交表单的方法不止一种,例如以任何文本输入为焦点按 Enter 键,更不用说编程提交了。
  • @tghw 我知道这是超级旧的,但希望你能看到它。有没有办法执行#2(将表单发布到您自己的服务器重定向页面,然后从那里发布到第三方)同时“保证”表单的值没有被修改。简单地隐藏这些字段会阻止人们以某种​​方式停止第二个 POST 并修改表单的值或完全提交新的 POST 请求吗?
【解决方案3】:

觉得分享一下 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>

【讨论】:

    【解决方案4】:

    这应该会让生活更轻松。 您可以轻松地在 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
    

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        你可以使用这个方法:

        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。

        【讨论】:

        • +1(如果可以的话,我会加你更多​​)。这就是答案。雄辩而切中要害。你甚至包括了所有的代码来做这件事,而不是胡思乱想。我在我的 aspx 页面上的 iframe 中使用了它,它完美地呈现了一切 - 没有重写 url。优秀作品!使用 iframe 的提示:我将 iframe 指向另一个 aspx 页面,然后执行此代码。
        • 支持执行此操作的方法是使用 307 是有原因的。POST 操作旨在成为幂等事务。这个答案只是一个碰巧有效的黑客攻击,将来很容易被浏览器阻止。
        • @sam 好点。作为反驳论点:根据最佳答案,IE 开发人员甚至没有阅读过 307。阅读它的其他人错误地实现了它。这意味着 307 显然会让聪明人感到困惑(假设浏览器开发人员很聪明)并且容易出现解释错误。上述方法很清楚,至少对我而言,它适用于过去和现在的所有浏览器。当我们开发人员与过去(阅读 IE7)和现在的进出斗争时,不必太担心未来。恕我直言,既然每个人都正确理解了它,他们应该保持原样。将来阻止它的理由是什么?
        • 至于幂等性,我认为期望应用程序处理重复项是合理的,如果需要,而不是依靠浏览器来执行它。数据库通过独特的约束概念使其变得容易。
        • 这与 TGHW 的第二个选项实际上有何不同?你不是也有可能会要求粗心的人引入 XSS 漏洞吗?
        【解决方案7】:

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

        )。
        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 往返的

        方式

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

        这个想法是你在重定向时调用带有某种状态的 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;
        }
        

        【讨论】:

          【解决方案8】:

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

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

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

          HTTP 规范的相关部分是9.1.1 and 9.1.210.3

          【讨论】:

          • 这应该是一条评论。
          【解决方案9】:

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

          是的,HttpWebRequest,请看我下面的帖子。

          【讨论】:

            【解决方案10】:

            这就是我要做的:

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

            【讨论】:

            • 这可行,但重要的是要注意您失去了所有 ASP.NET 服务器端功能。
            【解决方案11】:

            @马特,

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

            不过,这可能行得通。

            【讨论】:

              【解决方案12】:

              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,这可能有效,也可能无效。

              【讨论】:

              • 如果我想要一个额外的表单而不是 asp.net 表单,我会使用它吗?我需要将一些数据发布到用于 3d 安全支付的外部 url,然后我需要获取从请求返回的信息。这是这样做的方式吗?谢谢
              • 如果您需要用户查看响应,您可以发送回内容和任何适当的标头。您可能需要根据相关资源的使用情况来修改结果的某些方面,但这当然是可能的。
              • 用户可能拥有不会通过此方法传输的 cookie 数据,因为这是从服务器发生的。
              【解决方案13】:

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

              【讨论】:

                【解决方案14】:

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

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

                【讨论】:

                  猜你喜欢
                  • 2013-02-25
                  • 2011-08-04
                  • 1970-01-01
                  • 2016-12-25
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-03-12
                  相关资源
                  最近更新 更多