关于asp.net:Response.Redirect与POST而不是Get?

Response.Redirect with POST instead of Get?

我们需要提交表单并保存一些数据,然后将用户重定向到异地页面,但是在重定向过程中,我们需要"提交"一个带有post的表单,而不是get。

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

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

不管怎样,谢谢你的回答。


这样做需要了解HTTP重定向的工作方式。当您使用Response.Redirect()时,会发送一个响应(发送到发出请求的浏览器),其中HTTP状态代码302告诉浏览器下一步要去哪里。根据定义,浏览器将通过GET请求进行访问,即使原始请求是POST请求。

另一个选项是使用HTTP状态代码307,该代码指定浏览器应以与原始请求相同的方式发出重定向请求,但向用户提示安全警告。要做到这一点,您可以这样写:

1
2
3
4
5
6
7
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");
}

不幸的是,这并不总是奏效。不同的浏览器实现这一点是不同的,因为它不是常见的状态代码。

Alas, unlike the Opera and FireFox developers, the IE developers have never read the spec, and even the latest, most secure IE7 will redirect the POST request from domain A to domain B without any warnings or confirmation dialogs! Safari also acts in an interesting manner, while it does not raise a confirmation dialog and performs the redirect, it throws away the POST data, effectively changing 307 redirect into the more common 302.

所以,据我所知,实现这种功能的唯一方法就是使用JavaScript。有两种选择我可以考虑的最高点:

  • 创建表单并将其action属性指向第三方服务器。然后,向提交按钮添加一个单击事件,该事件首先使用数据向服务器执行Ajax请求,然后允许将表单提交到第三方服务器。
  • 创建要发布到服务器的表单。提交表单时,向用户显示一个页面,其中包含一个表单,其中包含所有要传递的数据,所有数据都在隐藏的输入中。只需显示类似"重定向…"的消息。然后,向向向第三方服务器提交表单的页面添加一个javascript事件。
  • 在这两者中,我选择第二个,有两个原因。首先,它比第一个更可靠,因为JavaScript不需要它来工作;对于那些没有启用它的人,您可以始终使隐藏表单的提交按钮可见,并指示他们在超过5秒时按下它。第二,您可以决定哪些数据被传输到第三方服务器;如果您使用的只是在表单经过时对其进行处理,那么您将传递所有的POST数据,这并不总是您想要的。307解决方案也是如此,假设它适用于所有用户。

    希望这有帮助!


    你可以用这个围裙:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Response.Clear();

    StringBuilder sb = new StringBuilder();
    sb.Append("<html>");
    sb.AppendFormat(@"<body onloadx='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。


    httpwebrequest用于此。

    在回发时,创建一个httpwebrequest到您的第三方并发布表单数据,然后一旦完成,您就可以响应。重定向到您想要的任何地方。

    您可以获得额外的优势,即不必命名所有服务器控件来生成第三方表单,您可以在构建post字符串时进行此转换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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,这可能有效,也可能无效。


    这会让生活更轻松。您可以简单地在Web应用程序中使用Response.RedirectWithData(…)方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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 onloadx='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中的一个新特性是ASP按钮的"postbackurl"属性。您可以将其设置为要直接发布到的页面的地址,单击该按钮时,它不会像正常一样重新发布到同一页面,而是发布到您指定的页面。手巧。确保useSubmitBehavior也设置为true。


    想分享一下Heroku用它的SSO来添加提供者可能会很有趣

    在"kensa"工具的源代码中可以看到它如何工作的示例:

    https://github.com/heroku/kensa/blob/d4556d50dcb2d26a4950081acda988937ee10/lib/heroku/kensa/post ou proxy.rb

    如果你使用javascript的话,你可以在实践中看到。示例页面源:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <!DOCTYPE HTML>
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        Heroku Add-ons SSO
      </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();
       
      </body>
    </html>


    可以在ASP按钮上设置回发URL,以便发布到其他页面。

    如果您需要在codebehind中执行此操作,请尝试server.transfer。


    get(and head)方法不应该用于做任何有副作用的事情。副作用可能是更新Web应用程序的状态,也可能是向您的信用卡收费。如果一个动作有副作用,则应使用另一种方法(post)。

    所以,用户(或者他们的浏览器)不应该为GET所做的事情负责。如果由于GET而产生一些有害或昂贵的副作用,那将是Web应用程序的错误,而不是用户的错误。根据规范,用户代理不能自动执行重定向,除非它是对GET或HEAD请求的响应。

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

    HTTP规范的相关章节为9.1.1、9.1.2和10.3。


    In PHP, you can send POST data with cURL. Is there something comparable for .NET?

    是的,httpwebrequest,请参阅下面的我的文章。


    我要做的是:

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


    @ Matt,

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

    不过,这可能会奏效。


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


    基于pavlo-neyman方法复制可粘贴代码

    RedirectPost(StringURL、tBodyPayload)和getPostData()是为那些只想在源页面中转储一些强类型数据并将其取回目标页面的用户提供的。数据必须可以通过newtonsoft json.net序列化,当然您需要参考这个库。

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

    我衷心感谢你们所有人,无论出于什么原因,他们在2019年仍然需要使用网络表单。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
            protected void RedirectPost(string url, IEnumerable<KeyValuePair<string,string>> fields)
            {
                Response.Clear();

                const string template =
    @"<html>
    <body onloadx='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(think

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

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

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

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

    我的HTTP有点生锈。我一直在浏览RFC2109和RFC2965,想知道这是多么的可靠,最好我希望cookie往返一次,但这似乎是不可能的,而且,第三方cookie可能是一个问题,如果你正在迁移到另一个域。这仍然是可能的,但并不像你在自己的领域内做事情那样无痛。

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

    这是一种在没有无意义的URL和javascript的情况下进行HTTP往返的方法。

    我将这段代码作为一个概念教授提供:如果这段代码是在一个您不熟悉的环境中运行的,我认为您可以计算出什么部分是什么。

    其思想是在重定向时使用某种状态调用relocate,而重新定位的URL调用get state以获取数据(如果有的话)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    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;
    }