URL 書き換えと PostBack と CallBack と

色々はまってました(笑) そんなわけで、まだコメントは登録できません。
URL 書き換えについては、Scott Mitchell さんの記事の和訳 MSDN - ASP.NET での URL 書き換え が十分な内容です。この文章では、URL 書き換えの手法として、IIS 側で ISAPI Filter を利用する以外に、ASP.NET 側からのアプローチとして、

  • HttpModule を利用する
  • HttpHandler を利用する
  • HttpHandlerFactory を利用する

という手法が紹介されています。ユーザ認証やファイル承認といったシステムとの関連で HttpModule を利用するのがベターである理由についても記載されています。

.

URL 書き換えが発生した後のページのレスポンスでは、PostBack 先は書き換え後の URL になっています。たとえば、例にあるような /people/ScottMitchell.aspx という URL に対して /info/employee.aspx?empID=1001 に書き換えが発生した場合、PostBack 先は /info/employee.aspx?empID=1001 になるため、PostBack が発生するとユーザに見える URL は /people/ScottMitchell.aspx から /info/employee.aspx?empID=1001 に置き換わってしまいます。*1
これに対する対策も、前述の文章にきちんと書かれていて、フォームの action 属性を削除してしまうことで対処するという内容になっていて、これは一見してうまく動きます。しかし、この手法には ASP.NET 2.0 になって増えた新しい2つの問題がありました。*2

  • デザイン画面が開けなくなる
  • クライアントコールバックが動作しなくなる

まず、前者については google で検索すると、やまのよーにひっかかる内容です。
ASP.NET 2.0 では、HTML コントロールに相当するタグと、Web コントロールに相当するタグで異なるデザイナを使用するようになりました。どちらも HtmlControlDesigner の派生クラスを使用するのですが、HTML コントロールは HtmlIntrinsicControlDesigner を使用し、Web コントロールは ControlDesigner の派生クラスを使用するという点が異なります。
HtmlForm の派生クラスを作成したことで、「HtmlIntrisicControlDesigner を使用する Web コントロール」という Visual Studio が扱えない状態のクラスができてしまい、実行時には問題は発生しないものの、設計時にデザイン画面を利用できなくなってしまいます。
この問題を解決するために、「デザイン時は HtmlForm クラスを生成し、実行時はカスタム Form クラスを生成する」という ControlBuilder を割り当てて HTML コントロールだと騙せるか試してみたり、デザイナとして System.Web.UI.Design.ContainerControlDesigner クラスを割り当ててまっとうな Web コントロールにみせてみたりしましたが、解消できませんでした。*3 *4
もう1つの問題は、昨日取り上げたクライアントコールバックが利用できなくなるという問題です。これは、クライアントコールバックのブラウザ側の実装で、xmlHttpRequest の要求先 URL を theForm.action に設定しているため、action 属性を省略した暗黙のポストバック先を使用していると、xmlHttpRequst が動作しなくなるという症状でした。

.

これらを色々やってたら、なんとか双方を同時に解決することができる方法を見つけました。ちょっと邪悪なのですが、

  • URL 書き換えの際に、RewritePath() に渡すパラメータのうち、ファイルパスを書き換えない
  • 書き換え先の aspx で、ファイルパスが不正でも動作するように考慮した実装を行う

です。RewritePath() でファイルパスを書き換えないため、標準の HtmlForm が書き換え前の URL を吐き出します。結果として、action 属性は、/people/ScottMitchell.aspx であれば、ScottMitchell.aspx になり、PostBack で URL が書き換わることもなく、 CallBack も動作するようになります。
このトリックを成立させるためには、2つ目の条件、つまり書き換え先の employee.aspx において、Request.CurrentExecutionFilePath をはじめとした、ファイルパスに関連する情報に依存しない実装を必要とします。たとえば「 .aspx ファイルと同じ場所においてある画像ファイル」みたいなファイルの検索を実施している場合に、要注意になります。

.

ついでに、RewritePath() に与える pathinfo の値も書き換えずにファイルパスに入れちゃうと、昨日の PathInformation を利用すると不正な URL へ PostBack する問題も解決します。pathinfo をファイルパスに含めたまま RewritePath() を実施すると、/file.aspx/path/info という URL で作成されるフォームの PostBack 先は info になるため、正常に Path Information を維持したまま PostBack が実施されるようになります。
当然ですが、不正なファイルパスを使用して RewritePath() を実施すると、Request.CurrentExecutionFilePath などが不正になるように、pathinfo が不正な RewitePath() を実施すると、Request.PathInfo で取得できる Path Information も不正になりますので、そのような状態を前提とした aspx の作成が必要になります。

*1:AutoPostBack であるか、通常の PostBack であるかに限らず発生します

*2:記事そのものは、明記されていませんが ASP.NET 1.1 が対象になっているとおもいます。

*3:ControlBuilder の生成結果にかかわらず、拡張タグはすべて Web コントロールだと判定されているため、HtmlIntrisicControlDesigner のインスタンスを ControlDesigner 型へキャストしようとしてしまいます。

*4:ContainerControlDesigner は生成され、Visual Studio と Designer の間に問題はなくなりますが、ターゲートコントロールが HTML コントロールのため、デザイナからターゲットコントロールへアクセスできないため、デザイナの上で例外が発生してしまいました。