GetではなくPOSTを使用したResponse.Redirect?


248

フォームを送信してデータを保存し、ユーザーをオフサイトのページにリダイレクトする必要がありますが、リダイレクトでは、GETではなくPOSTを使用してフォームを「送信」する必要があります。

これを達成する簡単な方法があることを望んでいましたが、そうではないと思い始めています。必要なフォームだけを含む単純な他のページを作成し、それにリダイレクトし、フォーム変数を設定してから、document.forms [0] .submit()を呼び出すだけのスクリプトに対してbody.onload呼び出しを実行する必要があると思います。 );

代替案があるかどうか誰かに教えてもらえますか?プロジェクトの後半でこれを微調整する必要があるかもしれませんし、それはちょっと複雑になるかもしれません。ですので、もし簡単にできれば、これは他のページに依存しない、すごいことです。

とにかく、ありとあらゆる回答に感謝します。


PHPでは、cURLを使用してPOSTデータを送信できます。.NETに相当するものはありますか?
ブライアンワルシャワ

これはあなたが探していた簡単な答えだと思います。私はそれがどれほど独創的であるか信じられませんでした... stackoverflow.com/a/6062248/110549
JoeCool

私はSystem.Net.Http.HttpClientを見つける@BrianWarshaw msdn.microsoft.com/en-us/library/...非常に直感的で迅速な用途に。
Stoyan Dimov 2016年

回答:


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

残念ながら、これは常に機能するとは限りません。 これは一般的なステータスコードではないため、ブラウザによって実装方法が異なります。

悲しいかな、OperaやFireFoxの開発者とは異なり、IEの開発者は仕様を読んだことがなく、最新の最も安全なIE7でも警告や確認のダイアログなしにドメインAからドメインBにPOSTリクエストをリダイレクトします。Safariも興味深い方法で動作しますが、確認ダイアログを表示せずにリダイレクトを実行しますが、POSTデータを破棄し、307リダイレクトをより一般的な302効果的に変更します。

したがって、私の知る限り、このようなものを実装する唯一の方法は、JavaScriptを使用することです。私の頭の上から考えることができる2つのオプションがあります。

  1. フォームを作成し、そのaction属性がサードパーティのサーバーを指すようにします。次に、最初にデータを使用してサーバーへのAJAXリクエストを実行し、次にフォームをサードパーティのサーバーに送信できるようにする送信ボタンにクリックイベントを追加します。
  2. サーバーに投稿するフォームを作成します。フォームが送信されたら、渡したいすべてのデータをすべて非表示の入力にして、フォームが含まれているページをユーザーに表示します。「リダイレクトしています...」のようなメッセージを表示するだけです。次に、フォームをサードパーティのサーバーに送信するページにJavaScriptイベントを追加します。

2つの理由から、2つ目を選択します。まず、Javascriptが機能するために必要とされないため、最初よりも信頼性が高くなります。有効にしていない場合は、いつでも非表示のフォームの送信ボタンを表示して、5秒以上かかる場合は押すように指示できます。次に、サードパーティのサーバーに送信するデータを決定できます。フォームを処理するだけで使用する場合、すべての投稿データが渡されますが、これは必ずしも必要なことではありません。すべてのユーザーで機能することを前提として、307ソリューションでも同じです。

お役に立てれば!


1
「送信ボタンにクリックイベントを追加する」を「フォームに送信イベントを追加する」に変更してください。フォームを送信する方法は複数あります。たとえば、プログラムによる送信はもちろん、テキスト入力に焦点を合わせてEnterキーを押します。
手元

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を取得した直後に、フォームの送信をトリガーし、すべてのデータを定義済みのpostbackUrlに送信するイベントのオンロードが行われます。


9
+1(できれば、さらにあなたにプラスします)。これが答えです。雄弁で要領を得ています。あなたは頭の上から話すのではなく、それを行うためのすべてのコードを含めました。私はこれを私のaspxページのiframeで使用し、すべてを完全にレンダリングしました-URLを書き換える必要はありません。素晴らしい仕事です!iframeを使用する場合のヒント:iframeを別のaspxページにポイントして、このコードを実行します。
MikeTeeVee

1
うまくいきます!私が目にする唯一の悪いことは、ブラウザーの戻るボタンを押すと、要求が再度実行されるため、実際には前のページにアクセスできなくなることです。そのための回避策はありますか?
山 シュナイダーズ

4
これを実行するためにサポートされている方法が307を使用しているのには理由があります。POSTアクションはべき等トランザクションであることを意図しています。この答えは、たまたま機能するハックであり、将来、ブラウザによって非常に簡単にブロックされる可能性があります。
Sam Rueby 2013

2
@sam良い点。反論として:上位の回答によると、IE開発者は307についてさえ読んでいませんでした。それを読んだ他の人はそれを間違って実装しました。つまり、307は賢く混乱し(ブラウザ開発者が賢いと仮定)、解釈エラーが発生しやすくなります。上記のアプローチは、少なくとも私には明らかで、過去と現在のすべてのブラウザーで機能します。私たちの開発者が過去(IE7を読む)と現在の出入りを戦うので、あまり心配していません。私見、誰もが正しく理解したので、そのままにしておくべきです。将来、それをブロックする理由は何でしょうか?
so_mv

2
これはTGHWの2番目のオプションと実質的に同じではないのですか?XSSの脆弱性を導入するために不注意な人を潜在的に求めているのではないですか?
スコット

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安全な支払いのための外部URLにデータを投稿する必要があります。その後、リクエストから返される情報を取得する必要があります。これはこれを行う方法ですか?ありがとう
Barbaros Alp

ユーザーに応答を表示する必要がある場合は、コンテンツと適切なヘッダーを送り返すだけです。相対的なリソースの使用状況によっては、結果の一部を変更する必要があるかもしれませんが、それは確かに可能です。
トーマスS.トリアス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

PostbackUrlをASPボタンに設定して、別のページに投稿できます。

コードビハインドで行う必要がある場合は、Server.Transferを試してください。


2

@マット、

引き続きHttpWebRequestを使用して、受け取った応答を実際の出力ストリーム応答に送信できます。これにより、応答がユーザーに返されます。唯一の問題は、相対URLが壊れることです。

それでもうまくいくかもしれません。


2

これが私がすることです:

データを標準フォーム(runat = "server"属性なし)に配置し、フォームのアクションをターゲットのオフサイトページに投稿するように設定します。送信する前に、XmlHttpRequest使用してサーバーにデータを送信し、応答を分析します。応答がオフサイトPOSTを続行する必要があることを意味する場合、私(JavaScript)は投稿を続行します。それ以外の場合は、自分のサイトのページにリダイレクトします


これは機能しますが、すべてのASP.NETサーバー側の機能が失われることに注意することが重要です。
senfo 2008

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

プログラムでPOSTを実行するHttpWebRequestを作成し、該当する場合は応答を読み取った後にリダイレクトすることをお勧めします。


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

通常、必要なのは、これら2つのリクエスト間で状態を運ぶことだけです。JavaScriptに依存しない、これを行うには本当にファンキーな方法があります(<noscript />を考えてください)。

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

そこにそのCookieがある場合、/ redirect.htmlへの次のリクエストでname = value情報を取得できます。この名前と値のペアの文字列には、最大4Kのデータ(通常のCookieの制限)まで、あらゆる種類の情報を格納できます。もちろん、これを避け、代わりにステータスコードとフラグビットを保存する必要があります。

このリクエストを受け取ると、代わりにそのステータスコードの削除リクエストで応答します。

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

私のHTTPは少し錆びています。RFC2109とRFC2965を使用して、これが実際にどの程度信頼できるかを把握してきました。できれば、Cookieを1回だけ正確に往復させたいのですが、それが可能ではないようです。サードパーティのCookie別のドメインに移動する場合は、問題になる可能性があります。これはまだ可能ですが、自分のドメイン内で何かをしているときほど痛みはありません。

ここでの問題は同時実行性です。パワーユーザーが複数のタブを使用していて、同じセッションに属する2、3のリクエストを交互にインターリーブすると(これは非常にまれですが、不可能ではありません)、アプリケーションの不整合につながる可能性があります。

これは、無意味な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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.