もちろん、同じプロセスで(たとえばHttpListenerで)ミニWebサーバーを起動し、それを介して要求をキャッチすることも可能ですが、それは単純すぎて退屈でスポーツマンらしくないです。 楽しみのために、ネットワークコンポーネントなしでやってみましょう。
そのため、タスクは簡単です。HTMLフォームの送信をインターセプトし、POSTパラメーターに応じてアプリケーションロジックによって生成された新しいHTMLドキュメントを出力する必要があります。
まず、単一のコントロールが存在する単一のフォームを備えたWindowsフォームアプリケーションが必要です。これは、すべてのスペースを占有するWebBrowserです。
たとえば、インターフェイスの簡単なページを2つ描画します(簡潔にするために最小限のコンテンツを追加し、好みに合わせてスクリプトとスタイルを追加します)。
最初のページ:
<!DOCTYPE html> <html> <head><meta http-equiv="X-UA-Compatible" content="IE=11"></head> <body> <form method="post"> <input type="text" name="TEXT1" value="Some text" /> <input type="submit" value="Open page 2" name="page2" /> </form> </body> </html>
2ページ目:
<!DOCTYPE html> <html> <head><meta http-equiv="X-UA-Compatible" content="IE=11"></head> <body> <div>%TEXT1%</div> <form method="post"> <input type="submit" value="Back to page 1" name="page1" /> </form> </body> </html>
注:X-UA-Compatibleは、一部のスタイルを正しく表示するために必要です。この例では使用されていませんが、問題があります。 詳細は説明しませんが、これがないと、コンポーネントは非常に古いものとの互換モードでページを描画し、その後のすべての結果をもたらします。
WebBrowserはいくつかのイベントを提供します。たとえば、Navigatingがあります。これは、フォームを送信する前またはリンクをたどる前に発生します。
ほとんど必要なものですが、このイベントは投稿パラメーターに関する情報を提供しません。 HTMLフォームにはaction属性がないため、GETパラメーターを使用できません。これにより、URLが空白になり、GETパラメーターに関する情報がなくなります。
リクエストに関するより詳細な情報を取得するには、ブラウザの内部COMオブジェクトでBeforeNavigate2イベント(詳細はこちら )をサブスクライブする必要があります。これは、ActiveXInstanceプロパティを通じて利用できるためです。
これを行う最も簡単な方法は、COMインターフェイスの宣言に煩わされないように、動的に行うことです。
デリゲートを宣言します。
private delegate void BeforeNavigate2(object pDisp, string url, int Flags, string TargetFrameName, byte[] PostData, string Headers, ref bool Cancel);
次に、イベントをサブスクライブします(WebBrowserフォームのコンストラクターでは、ActiveXInstanceプロパティはnullになるため、ウィンドウが読み込まれた後、つまりOnLoadなどでこれを行うことをお勧めします):
((dynamic)webBrowser.ActiveXInstance).BeforeNavigate2 += new BeforeNavigate2(OnBeforeNavigate2);
そのため、PostDataには、&を介して結合されたkey = valueのペアで構成される文字列形式の投稿パラメーターがあります。 それらを分離して辞書に入れます:
var parameters = (PostData == null ? string.Empty : Encoding.UTF8.GetString(PostData)) .Split('&') .Select(x => x.Split('=')) .Where(x => x.Length == 2) .ToDictionary(x => WebUtility.UrlDecode(x[0]), x => WebUtility.UrlDecode(x[1]));
さらに、このハンドラーでは、Cancelパラメーターを使用してアクションをキャンセルすることをお勧めします。これにより、再び空になります。
パラメーターを使用して、新しいページのテキストを生成します。 理論的には、特定のハンドラーマネージャーがここにねじ込まれ、いくつかのテンプレートに従って断片からページを収集するパラメーターに応じて必要なハンドラーを選択しますが、簡潔にするために、たとえば、ページpage1で最初のページを開き、page2ボタンで2番目のページ(名前マークアップでボタンを指定する必要があります。そうでない場合、どのボタンがポストパラメーターから押されたかを判別できません。また、括弧内のすべての行を次の名前のパラメーター値に置き換えます。
private static string Handler(IReadOnlyDictionary<string, string> parameters) { // do some useful stuff here var newPage = "Not found"; if (parameters.ContainsKey(nameof(page1))) newPage = page1; if (parameters.ContainsKey(nameof(page2))) newPage = page2; parameters.ToList().ForEach(x => newPage = newPage.Replace("{" + x.Key + "}", x.Value)); return newPage; }
結果のHTMLテキスト付き文字列は、WebBrowserのDocumentTextプロパティに書き込まれます。
そして、このプロパティを設定するとOnBeforeNavigate2の新しい呼び出しがトリガーされるため、ページのリロードサイクルが無限になります。
DocumentTextのインストールが制御を返した後、メッセージ処理サイクルのどこかから呼び出されるため、このイベントの一時的なサブスクライブ解除は機能しません。
T.O. 最初の操作は処理する必要のあるユーザーアクションの結果としてフォームを送信することであり、2番目の操作は処理する必要のない結果の表示であるため、ハンドラーの2番目の呼び出しごとに常に無視する必要があります。 簡単にするために、OnBeforeNavigate2ハンドラーの開始時にbool変数を切り替えます。
ignore = !ignore; if (ignore) return;
結果は次のとおりです。
最小限のアプリケーションに必要なのはこれだけです。 ただし、すべてがこのように機能するわけではありません。たとえば、入力タイプ= "ファイル"のファイルのデータを取得し、XMLHttpRequestを使用して、すべての種類のAJAXを含むスクリプトの正しい操作を行います。
ソースコード
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Windows.Forms; namespace TestHtmlUI { internal sealed class MainForm : Form { private const string page1 = @"<!DOCTYPE html> <html> <head><meta http-equiv=""X-UA-Compatible"" content=""IE=11""></head> <body> <form method = ""post"" > <input type=""text"" name=""TEXT1"" value=""Some text"" /> <input type=""submit"" value=""Open page 2"" name=""" + nameof(page2) + @""" /> </form> </body> </html>"; private const string page2 = @"<!DOCTYPE html> <html> <head><meta http-equiv=""X-UA-Compatible"" content=""IE=11""></head> <body> <div>{TEXT1}</div> <form method=""post""> <input type=""submit"" value=""Back to page 1"" name=""" + nameof(page1) + @""" /> </form> </body> </html>"; private delegate void BeforeNavigate2(object pDisp, string url, int Flags, string TargetFrameName, byte[] PostData, string Headers, ref bool Cancel); private readonly WebBrowser webBrowser = new WebBrowser { Dock = DockStyle.Fill }; private bool ignore = true; private MainForm() { StartPosition = FormStartPosition.CenterScreen; Controls.Add(webBrowser); Load += (sender, e) => ((dynamic)webBrowser.ActiveXInstance).BeforeNavigate2 += new BeforeNavigate2(OnBeforeNavigate2); webBrowser.DocumentText = Handler(new Dictionary<string, string> { { nameof(page1), string.Empty } }); } private void OnBeforeNavigate2(object pDisp, string url, int Flags, string TargetFrameName, byte[] PostData, string Headers, ref bool Cancel) { ignore = !ignore; if (ignore) return; Cancel = true; var parameters = (PostData == null ? string.Empty : Encoding.UTF8.GetString(PostData)) .Split('&') .Select(x => x.Split('=')) .Where(x => x.Length == 2) .ToDictionary(x => WebUtility.UrlDecode(x[0]), x => WebUtility.UrlDecode(x[1])); webBrowser.DocumentText = Handler(parameters); } [STAThread] private static void Main() { Application.EnableVisualStyles(); Application.Run(new MainForm()); } private static string Handler(IReadOnlyDictionary<string, string> parameters) { var newPage = "Not found"; if (parameters.ContainsKey(nameof(page1))) newPage = page1; if (parameters.ContainsKey(nameof(page2))) newPage = page2; // do dome usefull stuff here parameters.ToList().ForEach(x => newPage = newPage.Replace("{" + x.Key + "}", x.Value)); return newPage; } } }