Phản hồi. Chuyển hướng với POST thay vì Get?


248

Chúng tôi có yêu cầu gửi biểu mẫu và lưu một số dữ liệu, sau đó chuyển hướng người dùng đến trang bên ngoài, nhưng khi chuyển hướng, chúng tôi cần "gửi" biểu mẫu bằng POST, không phải GET.

Tôi đã hy vọng có một cách dễ dàng để thực hiện điều này, nhưng tôi bắt đầu nghĩ rằng không có. Tôi nghĩ bây giờ tôi phải tạo một trang khác đơn giản, chỉ với hình thức mà tôi muốn, chuyển hướng đến nó, điền vào các biến của biểu mẫu, sau đó thực hiện một cuộc gọi body.onload tới một tập lệnh chỉ gọi document.forms [0] .submit ( );

Bất cứ ai có thể cho tôi biết nếu có một sự thay thế? Chúng ta có thể cần phải điều chỉnh điều này sau này trong dự án và nó có thể trở nên phức tạp, vì vậy nếu có một cách dễ dàng, chúng ta có thể làm điều này tất cả các trang phụ thuộc không phải trang khác sẽ rất tuyệt vời.

Dù sao, cảm ơn cho bất kỳ và tất cả các phản ứng.


Trong PHP, bạn có thể gửi dữ liệu POST bằng cURL. Có cái gì đó có thể so sánh với .NET?
Brian Warshaw

Tôi nghĩ rằng đây là câu trả lời dễ dàng mà bạn đang tìm kiếm. Tôi không thể tin được nó khéo léo đến thế nào ... stackoverflow.com/a/6062248/110549
JoeCool 17/12/12

@BrianWarshaw Tôi tìm thấy System.Net.Http.HttpClient msdn.microsoft.com/en-us/l Library / trộm rất trực quan và nhanh chóng để sử dụng.
Stoyan Dimov

Câu trả lời:


228

Làm điều này đòi hỏi phải hiểu cách thức chuyển hướng HTTP hoạt động. Khi bạn sử dụng Response.Redirect(), bạn gửi phản hồi (tới trình duyệt đã thực hiện yêu cầu) với Mã trạng thái HTTP 302 , thông báo cho trình duyệt biết nơi tiếp theo sẽ đến. Theo định nghĩa, trình duyệt sẽ thực hiện điều đó thông qua một GETyêu cầu, ngay cả khi yêu cầu ban đầu là a POST.

Một tùy chọn khác là sử dụng Mã trạng thái HTTP 307 , chỉ định rằng trình duyệt sẽ thực hiện yêu cầu chuyển hướng giống như yêu cầu ban đầu, nhưng để nhắc nhở người dùng bằng cảnh báo bảo mật. Để làm điều đó, bạn sẽ viết một cái gì đó như thế này:

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

Thật không may, điều này sẽ không luôn luôn hoạt động. Các trình duyệt khác nhau thực hiện điều này khác nhau , vì nó không phải là một mã trạng thái phổ biến.

Than ôi, không giống như các nhà phát triển Opera và FireFox, các nhà phát triển IE chưa bao giờ đọc thông số kỹ thuật và ngay cả IE7 mới nhất, an toàn nhất sẽ chuyển hướng yêu cầu POST từ miền A sang miền B mà không có bất kỳ hộp thoại cảnh báo hoặc xác nhận nào! Safari cũng hoạt động theo cách thú vị, trong khi nó không đưa ra hộp thoại xác nhận và thực hiện chuyển hướng, nó sẽ loại bỏ dữ liệu POST, thay đổi hiệu quả 307 chuyển hướng thành 302 phổ biến hơn.

Vì vậy, theo như tôi biết, cách duy nhất để thực hiện một cái gì đó như thế này là sử dụng Javascript. Có hai lựa chọn tôi có thể nghĩ ra khỏi đỉnh đầu:

  1. Tạo biểu mẫu và có actionđiểm thuộc tính của nó đến máy chủ của bên thứ ba. Sau đó, thêm một sự kiện nhấp chuột vào nút gửi trước tiên thực hiện yêu cầu AJAX đến máy chủ của bạn với dữ liệu và sau đó cho phép biểu mẫu được gửi đến máy chủ của bên thứ ba.
  2. Tạo biểu mẫu để gửi đến máy chủ của bạn. Khi biểu mẫu được gửi, hãy hiển thị cho người dùng một trang có biểu mẫu trong đó với tất cả dữ liệu bạn muốn truyền vào, tất cả trong các đầu vào ẩn. Chỉ cần hiển thị một thông báo như "Chuyển hướng ...". Sau đó, thêm một sự kiện javascript vào trang gửi biểu mẫu đến máy chủ của bên thứ ba.

Trong hai, tôi sẽ chọn cái thứ hai, vì hai lý do. Đầu tiên, nó đáng tin cậy hơn lần đầu tiên vì Javascript không bắt buộc để nó hoạt động; Đối với những người không kích hoạt nó, bạn luôn có thể hiển thị nút gửi cho biểu mẫu ẩn và hướng dẫn họ nhấn nó nếu mất hơn 5 giây. Thứ hai, bạn có thể quyết định dữ liệu nào được truyền đến máy chủ của bên thứ ba; nếu bạn sử dụng chỉ cần xử lý biểu mẫu khi nó đi qua, bạn sẽ chuyển qua tất cả dữ liệu bài đăng, đây không phải lúc nào cũng là điều bạn muốn. Tương tự cho giải pháp 307, giả sử nó hoạt động cho tất cả người dùng của bạn.

Hi vọng điêu nay co ich!


1
Vui lòng thay đổi "thêm sự kiện nhấp vào nút gửi" thành "thêm sự kiện gửi vào biểu mẫu". Có nhiều hơn một phương thức để gửi biểu mẫu, ví dụ: nhấn Enter với bất kỳ kiểu nhập văn bản nào được tập trung, hãy để một mình gửi theo chương trình.
temoto

122

Bạn có thể sử dụng aproach này:

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();

Kết quả là ngay sau khi máy khách sẽ nhận được tất cả html từ máy chủ, sự kiện onload diễn ra sẽ kích hoạt biểu mẫu gửi và gửi tất cả dữ liệu lên postbackUrl được xác định.


9
+1 (Tôi sẽ cộng với bạn nhiều hơn nếu tôi có thể). Đây là câu trả lời. Eloquent và đến điểm. Bạn thậm chí bao gồm tất cả các mã để làm điều đó thay vì nói ra khỏi đỉnh đầu của bạn. Tôi đã sử dụng điều này trong iframe trên trang aspx của mình và nó hiển thị mọi thứ hoàn hảo - không viết lại các url. Làm tốt lắm MIPO cho những người sử dụng iframe: Tôi trỏ iframe của mình đến một trang aspx khác để thực thi mã này.
MikeTeeVee

1
Nó không hoạt động! Điều tồi tệ duy nhất tôi thấy là nếu bạn nhấn nút quay lại của trình duyệt, nó sẽ thực hiện lại yêu cầu, vì vậy bạn không thể thực sự truy cập trang trước đó nữa. Có một cách giải quyết cho điều đó?
Mt. Schneiders

4
Có một lý do phương pháp được hỗ trợ để thực hiện việc này là sử dụng 307. Các hành động POST được dự định là các giao dịch tạm thời. Câu trả lời này chỉ đơn thuần là một bản hack xảy ra để hoạt động và có thể rất dễ bị chặn bởi các trình duyệt trong tương lai.
Sam Ruither

2
@sam điểm tốt. Như một đối số: theo câu trả lời hàng đầu, nhà phát triển IE thậm chí không đọc khoảng 307. Những người khác đọc nó đã thực hiện sai. Điều đó có nghĩa là 307 rõ ràng gây nhầm lẫn cho các thông minh (giả sử các nhà phát triển trình duyệt là thông minh) và dễ bị lỗi diễn giải. Cách tiếp cận trên là rõ ràng, ít nhất là với tôi và nó hoạt động trong tất cả các trình duyệt trong quá khứ và hiện tại. Không quá lo lắng về tương lai khi các nhà phát triển của chúng tôi chiến đấu với quá khứ (đọc IE7) và hiện tại vào / ra. IMHO, vì mọi người đều hiểu đúng nên họ nên giữ nguyên. Điều gì sẽ là lý do để chặn nó, trong tương lai?
so_mv

2
Làm thế nào điều này không hiệu quả giống như tùy chọn thứ hai của TGHW? Không phải bạn cũng có khả năng yêu cầu ai đó bất cẩn giới thiệu lỗ hổng XSS sao?
Scott

33

HttpWebRequest được sử dụng cho việc này.

Khi gửi lại, hãy tạo một httpWebRequest cho bên thứ ba của bạn và đăng dữ liệu biểu mẫu, sau đó khi hoàn thành, bạn có thể Phản hồi. Chuyển hướng bất cứ nơi nào bạn muốn.

Bạn nhận được lợi thế bổ sung mà bạn không phải đặt tên cho tất cả các điều khiển máy chủ của mình để tạo thành bên thứ 3, bạn có thể thực hiện bản dịch này khi xây dựng chuỗi 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");

Tuy nhiên, nếu bạn cần người dùng xem trang phản hồi từ biểu mẫu này, tùy chọn duy nhất của bạn là sử dụng Server.Transfer và điều đó có thể hoặc không thể hoạt động.


đó có phải là những gì tôi sẽ sử dụng nếu tôi muốn một hình thức bổ sung hơn hình thức asp.net. Tôi cần đăng một số dữ liệu lên một url bên ngoài để thanh toán an toàn 3d, sau đó tôi cần nhận thông tin trả về từ yêu cầu. Đây có phải là cách làm điều này? Cảm ơn bạn
Barbaros Alp

Nếu bạn cần người dùng xem phản hồi, bạn có thể gửi lại nội dung và bất kỳ tiêu đề thích hợp nào. Bạn có thể phải sửa đổi một số khía cạnh của kết quả tùy thuộc vào việc sử dụng tài nguyên tương đối, nhưng chắc chắn là có thể.
Thomas S. Trias

6
Người dùng có thể có dữ liệu cookie sẽ không được truyền qua phương thức này, vì điều này đang xảy ra từ máy chủ.
Scott

7

Điều này sẽ làm cho cuộc sống dễ dàng hơn nhiều. Bạn chỉ có thể sử dụng phương thức Feedback.RedirectWithData (...) trong ứng dụng web của mình một cách dễ dàng.

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

Một cái gì đó mới trong ASP.Net 3.5 là thuộc tính "PostBackUrl" của các nút ASP. Bạn có thể đặt nó vào địa chỉ của trang bạn muốn đăng trực tiếp và khi nhấp vào nút đó, thay vì đăng trở lại cùng một trang như bình thường, thay vào đó, nó sẽ đăng lên trang bạn đã chỉ định. Tiện dụng. Hãy chắc chắn UseSubmitBehavior cũng được đặt thành TRUE.


4

Nghĩ rằng sẽ rất thú vị khi chia sẻ rằng heroku làm điều này với SSO cho các nhà cung cấp tiện ích bổ sung

Một ví dụ về cách thức hoạt động của nó có thể được nhìn thấy trong nguồn tới công cụ "kensa":

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

Và có thể được nhìn thấy trong thực tế nếu bạn bật javascript. Nguồn trang ví dụ:

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

PostbackUrl có thể được đặt trên nút asp của bạn để đăng lên một trang khác.

nếu bạn cần làm điều đó trong codebehind, hãy thử Server.Transfer.


2

@Matt,

Bạn vẫn có thể sử dụng HttpWebRequest, sau đó hướng phản hồi bạn nhận được đến phản hồi đầu ra thực tế, điều này sẽ phục vụ phản hồi lại cho người dùng. Vấn đề duy nhất là bất kỳ url tương đối sẽ bị phá vỡ.

Tuy nhiên, điều đó có thể làm việc.


2

Đây là những gì tôi sẽ làm:

Đặt dữ liệu ở dạng chuẩn (không có thuộc tính runat = "server") và đặt hành động của biểu mẫu để đăng lên trang ngoài trang đích. Trước khi gửi, tôi sẽ gửi dữ liệu đến máy chủ của mình bằng XmlHttpRequest và phân tích phản hồi. Nếu phản hồi có nghĩa là bạn nên tiếp tục với POSTing ngoại vi thì tôi (JavaScript) sẽ tiếp tục với bài đăng nếu không tôi sẽ chuyển hướng đến một trang trên trang web của tôi


Điều này hoạt động, nhưng điều quan trọng cần lưu ý là bạn mất tất cả chức năng phía máy chủ ASP.NET.
senfo

2

Trong PHP, bạn có thể gửi dữ liệu POST bằng cURL. Có cái gì đó có thể so sánh với .NET?

Vâng, httpWebRequest, xem bài viết của tôi dưới đây.


2

Không bao giờ nên sử dụng phương thức GET (và HEAD) để làm bất cứ điều gì có tác dụng phụ. Một tác dụng phụ có thể là cập nhật trạng thái của ứng dụng web hoặc có thể tính phí thẻ tín dụng của bạn. Nếu một hành động có tác dụng phụ thì nên sử dụng phương thức khác (POST).

Vì vậy, người dùng (hoặc trình duyệt của họ) không nên chịu trách nhiệm cho những việc được thực hiện bởi GET. Nếu một số tác dụng phụ có hại hoặc đắt tiền xảy ra do kết quả của GET, đó sẽ là lỗi của ứng dụng web chứ không phải người dùng. Theo thông số kỹ thuật, một tác nhân người dùng không được tự động theo chuyển hướng trừ khi đó là phản hồi cho yêu cầu GET hoặc HEAD.

Tất nhiên, rất nhiều yêu cầu GET có một số tác dụng phụ, ngay cả khi nó chỉ thêm vào một tệp nhật ký. Điều quan trọng là ứng dụng, chứ không phải người dùng, phải chịu trách nhiệm cho những hiệu ứng đó.

Các phần có liên quan của thông số HTTP là 9.1.1 và 9.1.210.3 .


1

Tôi khuyên bạn nên xây dựng một httpWebRequest để lập trình POST của bạn và sau đó chuyển hướng sau khi đọc Phản hồi nếu có.


1

Mã có thể sao chép dựa trên phương pháp của Pavlo Neyman

RedirectPost (url chuỗi, T bodyPayload) và GetPostData () dành cho những người chỉ muốn đổ một số dữ liệu được gõ mạnh vào trang nguồn và tìm nạp lại trong mục tiêu. Dữ liệu phải được tuần tự hóa bởi NewtonSoft Json.NET và tất nhiên bạn cần tham khảo thư viện.

Chỉ cần sao chép-dán vào (các) trang của bạn hoặc lớp cơ sở tốt hơn cho các trang của bạn và sử dụng nó ở bất cứ đâu trong ứng dụng của bạn.

Trái tim tôi hướng về tất cả các bạn vẫn phải sử dụng Biểu mẫu web vào năm 2019 vì bất kỳ lý do gì.

        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

Thông thường, tất cả những gì bạn cần là thực hiện một số trạng thái giữa hai yêu cầu này. Thực sự có một cách thực sự thú vị để làm điều này mà không dựa vào JavaScript (nghĩ <noscript />).

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

Với cookie đó, bạn có thể yêu cầu /redirect.html lấy thông tin name = value sau đây, bạn có thể lưu trữ bất kỳ loại thông tin nào trong chuỗi cặp tên / giá trị này, tối đa là 4K dữ liệu (giới hạn cookie thông thường). Tất nhiên, bạn nên tránh điều này và lưu trữ mã trạng thái và các bit cờ thay thế.

Khi nhận được yêu cầu này, bạn sẽ đáp lại yêu cầu xóa mã trạng thái đó.

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

HTTP của tôi hơi bị rỉ sét Tôi đã sử dụng RFC2109 và RFC2965 để tìm hiểu mức độ đáng tin cậy của nó, tốt nhất là tôi muốn cookie thực hiện một chuyến đi chính xác một lần nhưng dường như cũng không thể, cookie của bên thứ ba có thể là một vấn đề cho bạn nếu bạn chuyển sang tên miền khác. Điều này vẫn có thể nhưng không đau đớn như khi bạn làm công việc trong miền của riêng bạn.

Vấn đề ở đây là sự tương tranh, nếu một người dùng quyền lực đang sử dụng nhiều tab và quản lý để xen kẽ một vài yêu cầu thuộc cùng một phiên (điều này rất khó xảy ra, nhưng không phải là không thể), điều này có thể dẫn đến sự không nhất quán trong ứng dụng của bạn.

Đó là cách <noscript /> thực hiện các chuyến đi vòng HTTP mà không cần URL và JavaScript vô nghĩa

Tôi cung cấp mã này dưới dạng khái niệm: Nếu mã này được chạy trong ngữ cảnh mà bạn không quen thuộc, tôi nghĩ bạn có thể tìm ra phần nào là gì.

Ý tưởng là bạn gọi Relocate với một số trạng thái khi bạn chuyển hướng và URL mà bạn đã di chuyển gọi GetState để lấy dữ liệu (nếu có).

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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.