ASP.NET での URL 書き換え

短期集中コース

Scott Mitchell
4GuysFromRolla.com

March 2004
日本語版最終更新日 2004 年 8 月 6 日

適用対象:
   Microsoft® ASP.NET

要約: Microsoft ASP.NET で動的に URL 書き換えを行う方法について説明します。URL 書き換えとは、受信 Web 要求をインターセプトして、自動的に別の URL にリダイレクトするプロセスです。URL 書き換えを実装するさまざまな技法を説明してから、実際に URL 書き換えを使用するケースについて考察します。サンプル プログラム ファイル内では実際のコメント行は英語で書かれていますが、この記事内では説明目的で日本語で書かれています。この記事には英語のページへのリンクも含まれています。

この記事のソース コードをダウンロードする

目次

はじめに
URL 書き換えの一般的な使用法
要求が IIS に到達したときの処理
URL 書き換えを実装する
URL 書き換えエンジンを作成する
URL 書き換えエンジンを使用して簡単な URL 書き換えを実行する
実際に "短縮可能" な URL を作成する
まとめ
関連書籍

はじめに

Web サイトで使用されている URL をいくつか調べてみてください。http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099&type=summary といった URL が使用されていませんか。あるいは、ひとまとまりの Web ページを別のディレクトリや Web サイトに移動したために、以前の URL をブックマークしているサイト利用者のリンクが壊れていることはありませんか。この記事では、"http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099&type=summary" といった長くて面倒な URL を、"http://yoursite.com/people/sales/chuck.smith" といった意味のある覚えやすい URL に短縮する "URL 書き換え" について説明します。また、URL 書き換えによって、インテリジェントな 404 エラーを作成する方法についても見ていきます。

URL 書き換えとは、受信 Web 要求をインターセプトして、自動的に別のリソースにリダイレクトするプロセスです。URL 書き換えを実行すると、通常は、要求されている URL がチェックされて、URL の値に基づいて要求が別の URL にリダイレクトされます。たとえば、Web サイトを再構築したために、/people/ ディレクトリ内の Web ページ全部を /info/employees/ ディレクトリに移動した場合には、URL 書き換えを使用して、まず Web 要求が /people/ ディレクトリ内のファイルを要求したものであるかどうかを確認します。そして、/people/ ディレクトリ内のファイルを要求したものである場合は、その要求を /info/employees/ ディレクトリ内の同じファイルに自動的にリダイレクトする必要があります。

従来の ASP では、URL 書き換えを利用するには、ISAPI フィルタを記述するか、URL 書き換え機能を提供するサードパーティ製品を購入するしか方法はありませんでした。しかし、Microsoft ASP.NET では、さまざまな方法で URL 書き換えを行う独自のソフトウェアを簡単に作成できます。この記事では、ASP.NET 開発者が URL 書き換えを実装する際に使用できる技法を説明してから、実際に URL 書き換えを使用する事例について説明します。URL 書き換えの技術的な詳細を説明する前に、まず URL 書き換えを利用できる日常的なシナリオについて見てみましょう。

URL 書き換えの一般的な使用法

データ ドリブン型の ASP.NET Web サイトを作成すると、多くの場合、クエリ文字列パラメータに基づいて 1 つの Web ページにデータベースのデータのサブセットが表示されます。たとえば、電子商取引サイトを設計するときには、作業の 1 つとして、ユーザーが販売中の製品を閲覧できるようにする処理が必要になります。この処理を簡単に行うために、指定されたカテゴリの製品を表示する displayCategory.aspx というページを作成できます。表示するカテゴリに含まれる製品は、クエリ文字列パラメータで指定されます。つまり、販売されている製品の中でユーザーが閲覧しようとする製品の CategoryID が 5 であるとすると、ユーザーは http://yousite.com/displayCategory.aspx?CategoryID=5 にアクセスすることになります。

このような URL で Web サイトを作成する場合、次のような 2 つの欠点があります。まず、エンド ユーザーの立場からすると、http://yousite.com/displayCategory.aspx?CategoryID=5 という URL は、ごちゃごちゃしてわかりにくい URL です。ユーザビリティのエキスパートである Jakob NeilsenNon-MS link (英語) は、次のような URL を選択すべきであると推奨しています Non-MS link (英語)。

  • 短い。
  • 入力が簡単である。
  • URL を見ると、サイトの構造がわかる。
  • "短縮可能 (hackable)" である。つまり、ユーザーが URL の一部分を入力すれば、そのサイトを表示して目的のページに移動できる。

私は、このリストに "覚えやすい" という項目も追加したいと思います。http://yousite.com/displayCategory.aspx?CategoryID=5 という URL は、Neilsen 氏が挙げた基準のいずれも満たしていないばかりか、覚えやすいものでもありません。ユーザーにクエリ文字列値の入力を求めると、URL は入力しにくいものになり、URL を "短縮可能" にできるのは、クエリ文字列パラメータと、その名前と値を組み合わせた構造の目的を理解している経験豊富な Web 開発者だけになってしまいます。

これを改善するには、http://yoursite.com/products/Widgets といった、わかりやすく覚えやすい URL を使用できるようにします。URL を見ただけで、何が表示されるか、製品に関する情報が推測できるようになります。また、こういった URL は覚えやすく、他の人に伝えやすい URL でもあります。私が同僚に「yoursite.com/products/Widgets を調べてください」と依頼すると、おそらく同僚は URL が何であったか私に聞き直すことなく、そのページを表示できることでしょう。たとえば、Amazon.com ページで試してみてください。また同時に、URL は表示されるだけでなく、"短縮可能" でなければなりません。つまり、ユーザーが URL の末尾を省略して http://yoursite.com/products と入力した場合、"すべての" 製品の一覧が表示されるか、少なくとも表示可能な製品の全カテゴリの一覧が表示される必要があります。

注: "短縮可能" な URL の代表的な例について、多くの blog エンジンで生成される URL を考えてみましょう。2004 年 1 月 28 日の投稿を表示するには、http://someblog.com/2004/01/28 という URL にアクセスします。この URL の末尾を省略して http://someblog.com/2004/01 と入力すると、2004 年 1 月のすべての投稿が表示されます。さらに http://someblog.com/2004 と省略すると、2004 年のすべての投稿が表示されます。

URL 書き換えは URL を簡略化するためだけでなく、Web サイトの再構築を処理する際にもよく使用されます。URL 書き換えを行わないと、膨大な壊れたリンクと無効なブックマークが生じることになります。

要求が IIS に到達したときの処理

URL 書き換えの実装方法について調べる前に、受信要求が Microsoft Internet Information Services (IIS) でどのように処理されるかを理解しておくことが重要です。要求が IIS Web サーバーに着信すると、IIS では要求されているファイルの拡張子を調べて、その要求の処理方法を決定します。IIS では、要求を HTML ページ、イメージ、およびその他のスタティック コンテンツとしてネイティブに処理したり、要求を ISAPI 拡張にルーティングすることができます。ISAPI 拡張は、受信 Web 要求を処理するコンパイル済みのアンマネージ クラスです。ISAPI 拡張のタスクは、要求されたリソースに対するコンテンツを生成することです。

たとえば、IIS は Info.asp という Web ページに対する要求を受信すると、asp.dll ISAPI 拡張にそのメッセージをルーティングします。そして、この ISAPI 拡張が要求された ASP ページを読み込んで実行し、レンダリングした HTML ページを IIS に返します。そして、受け取った IIS がこの HTML ページを要求元のクライアントに送り返します。ASP.NET ページの場合には、IIS はメッセージを aspnet_isapi.dll ISAPI 拡張にルーティングします。そして、aspnet_isapi.dll ISAPI 拡張では、マネージ ASP.NET ワーカー プロセスに処理を渡し、このプロセスで要求が処理されて、ASP.NET Web ページをレンダリングした HTML ページが返されます。

IIS をカスタマイズして、ISAPI 拡張にマップされる拡張子を指定することができます。図 1 は、インターネット インフォメーション サービス (IIS) の管理ツールの [アプリケーションの構成] ダイアログ ボックスです。.aspx、.ascx、.config、.asmx、.rem、.cs、.vb などの ASP.NET 関連の拡張子はすべて aspnet_isapi.dll ISAPI 拡張にマップされています。

図 1. ファイル拡張子に対して構成されているマッピング

IIS で受信要求を管理する詳細な方法については、この記事では説明しません。詳細な解説については、Michele Leroux Bustamante 氏の記事「Inside IIS and ASP.NET Non-MS link (英語)」を参照してください。ASP.NET エンジンは、拡張子が IIS で明示的に aspnet_isapi.dll にマップされている受信 Web 要求のみ処理することを理解することが大切です。

ISAPI フィルタを使用して要求を調べる

IIS は、受信 Web 要求のファイル拡張子を適切な ISAPI 拡張にマップするだけでなく、その他にも多くのタスクを実行します。たとえば IIS では、要求を作成するユーザーの認証を行い、認証されたユーザーが要求されているファイルへのアクセスを承認されているかどうかを判断します。IIS は要求を処理する間に、いくつかの状態を経過します。いずれの状態でも、IIS は、ISAPI フィルタを使用してプログラムによって処理できるイベントを発生します。

ISAPI 拡張と同様に、ISAPI フィルタも Web サーバーにインストールされているアンマネージ コード ブロックです。ISAPI 拡張は、特定のファイルの種類への要求に対する応答を生成するために設計された機能です。一方、ISAPI フィルタには、IIS で発生したイベントに応答するコードが含まれます。ISAPI フィルタは、受信データと送信データをインターセプトするだけでなく、変更することもできます。ISAPI フィルタには、次のような多くの用途があります。

  • 認証と承認
  • ログ出力と監視
  • HTTP 圧縮
  • URL 書き換え

ISAPI フィルタを使用して URL 書き換えを行うこともできますが、この記事では ASP.NET を使用した URL 書き換えの実装について説明します。ただし、ISAPI フィルタとして URL 書き換えを実装した場合と、ASP.NET で使用できる技法を使用して実装した場合のトレードオフについては説明します。

要求が ASP.NET エンジンに到達したときの処理

ASP.NET 以前では、IIS Web サーバーで URL 書き換えを実装するには、ISAPI フィルタを使用する必要がありました。ASP.NET を使用した URL 書き換えが可能なのは、ASP.NET エンジンが IIS にきわめて類似しているためです。類似しているのは、次のような ASP.NET エンジンの特性によるものです。

  1. 要求を処理するときに、イベントを発生する。
  2. 発生したイベントの処理を、任意の数の "HTTP モジュール" で実行できる (IIS の ISAPI フィルタに類似)。
  3. 要求されているリソースのレンダリングを "HTTP ハンドラ" に委任する (IIS の ISAPI 拡張に類似)。

IIS と同様に ASP.NET エンジンも、要求の処理中にイベントを発生させて、次の処理状態への変更を通知します。たとえば、ASP.NET エンジンが最初に要求に応答するときに、BeginRequest イベントが発生します。次に ユーザー ID が確立されると、AuthenticateRequest イベントが発生します。この他にも、AuthorizeRequest、ResolveRequestCache、EndRequest などの多くのイベントがあります。これらのイベントは、System.Web.HttpApplication クラスのイベントです。詳細については、「HttpApplication クラス」の技術文書を参照してください。

前のセクションで説明したように、ISAPI フィルタを作成すると、IIS で発生したイベントに応答できます。同様に、ASP.NET にも、ASP.NET エンジンで発生したイベントに応答する "HTTP モジュール" が用意されています。1 つの ASP.NET Web アプリケーションに対して、複数の HTTP モジュールを構成することもできます。ASP.NET エンジンで各要求を処理するたびに、構成されている各 HTTP モジュールが初期化されて、イベント ハンドラを、要求の処理中に発生したイベントに割り当てることができます。どの要求に対しても使用できる組み込み HTTP モジュールがいくつか用意されています。その 1 つの組み込み HTTP モジュールが FormsAuthenticationModule です。このモジュールは、フォーム認証が使用されているかどうかを調べて、使用されている場合には、ユーザーが認証されているかどうかを確認します。認証されていない場合、自動的にユーザーは指定したログオン ページにリダイレクトされます。

IIS では、最終的に受信要求は ISAPI 拡張に送られて、そこから特定の要求に対するデータが返されることを思い出してください。たとえば、IIS は従来の ASP Web ページに対する要求を受信すると、その要求を asp.dll ISAPI 拡張に渡します。ISAPI 拡張からは、要求された ASP ページに対する HTML マークアップが返されます。ASP.NET エンジンも同様の方法を使用しています。ASP.NET エンジンでは、HTTP モジュールの初期化が済むと、要求を処理する "HTTP ハンドラ" を決定します。

ASP.NET エンジンを通過した要求はすべて、最終的に HTTP ハンドラ、または HTTP ハンドラ ファクトリに到着します (HTTP ハンドラ ファクトリでは、要求の処理に使用される HTTP ハンドラのインスタンスを返すだけです)。最終的に HTTP ハンドラが要求されたリソースをレンダリングして、応答を返します。この応答が IIS に送り返されて、IIS から要求元のユーザーにこの応答が返されます。

ASP.NET には、いくつかの組み込み HTTP ハンドラがあります。たとえば、PageHandlerFactory は ASP.NET Web ページのレンダリングに使用されます。WebServiceHandlerFactory は、ASP.NET Web サービスに対する応答の SOAP エンベロープのレンダリングに使用されます。また、TraceHandler では、trace.axd への要求に対する HTML マークアップがレンダリングされます。

図 2 は、ASP.NET リソースへの要求がどのように処理されるかを示しています。まず、IIS で要求を受信して、この要求を aspnet_isapi.dll にディスパッチします。次に ASP.NET エンジンが構成されている HTTP モジュールを初期化します。さらに、適切な HTTP ハンドラが呼び出されて、要求されたリソースがレンダリングされ、最後に、生成されたマークアップが IIS に送り返されて、要求元のクライアントに返されます。

図 2. IIS と ASP.NET による要求の処理

カスタムの HTTP モジュールと HTTP ハンドラを作成して登録する

カスタムの HTTP モジュールと HTTP ハンドラを作成するのは比較的簡単なタスクですが、このタスクには、正しいインターフェイスを実装するマネージ クラスの作成が含まれます。HTTP モジュールでは System.Web.IHttpModule インターフェイスを実装しなければならず、さらに、HTTP ハンドラと HTTP ハンドラ ファクトリでは、それぞれ System.Web.IHttpHandler インターフェイスと System.Web.IHttpHandlerFactory インターフェイスを実装しなければなりません。HTTP ハンドラと HTTP モジュールの詳細な作成方法については、この記事では扱いません。詳細については、Mansoor Ahmed Siddiqui 氏の記事「HTTP Handlers and HTTP Modules in ASP.NET Non-MS link (英語)」を参照してください。

カスタムの HTTP モジュールや HTTP ハンドラを作成したら、Web アプリケーションを使用して登録する必要があります。Web サーバー全体に HTTP モジュールと HTTP ハンドラを登録するときは、machine.config ファイルに追加するだけですが、特定の Web アプリケーションに HTTP モジュールや HTTP ハンドラを登録するときは、そのアプリケーションの Web.config ファイルに 数行の XML を追加する必要があります。

特に、HTTP モジュールを Web アプリケーションに追加するときには、Web.config ファイルの configuration/system.web セクションに次の数行を追加します。

<httpModules>
<add type="種類" name="名前" />
</httpModules>

"種類" の値は HTTP モジュールのアセンブリおよびクラス名で、"名前" の値は、Global.asax ファイルで HTTP モジュールを参照するときのわかりやすい名前です。

HTTP ハンドラと HTTP ハンドラ ファクトリは、Web.config ファイルの configuration/system.web セクションでは <httpHandlers> タグを使って次のように構成します。

<httpHandlers>
<add verb="動詞" path="パス" type="種類" />
</httpHandlers>

ASP.NET エンジンでは、受信要求ごとに要求のレンダリングに使用する HTTP ハンドラを決定します。この HTTP ハンドラの決定は、受信要求の動詞とパスに基づいて行われます。動詞には HTTP 要求の種類を示す GET または POST のいずれかを指定し、パスには要求されているファイルの場所とファイル名を指定します。したがって、.scott という拡張子を持つファイルに対するすべての要求 (GET または POST のいずれか) を HTTP ハンドラで処理する場合は、Web.config ファイルに次の行を追加することになります。

<httpHandlers>
<add verb="*" path="*.scott" type="種類" />
</httpHandlers>

"種類" は、HTTP ハンドラの種類です。

注: HTTP ハンドラを登録する場合、HTTP ハンドラで使用する拡張子が IIS で ASP.NET エンジンにマップされていることを確認することが重要です。つまり、この .scott の例で .scott 拡張子が IIS で aspnet_isapi.dll ISAPI 拡張にマップされていない場合、ファイル foo.scott に対する要求を行うと、IIS ではファイル foo.scott のコンテンツを返そうとしてしまいます。HTTP ハンドラでこの要求を処理するためには、.scott 拡張子が ASP.NET エンジンにマップされていなければなりません。マップされていれば、要求は ASP.NET エンジンによって該当する HTTP ハンドラに正しく送信されます。

HTTP モジュールと HTTP ハンドラの登録方法の詳細については、「<httpModules> 要素」と「<httpHandlers> 要素」のドキュメントを参照してください。

URL 書き換えを実装する

URL 書き換えは、IIS Web サーバー レベルで ISAPI フィルタを使用しても、ASP.NET レベルで HTTP モジュールまたは HTTP ハンドラのいずれかを使用しても実装できます。この記事では、ASP.NET による URL 書き換えの実装を中心に説明しているので、ISAPI フィルタによる URL 書き換えの実装については詳細には説明しません。ただし、次のような URL 書き換え用の ISAPI フィルタが、サードパーティから多数提供されています。

ASP.NET レベルで URL 書き換えを実装するときは、System.Web.HttpContext クラスの RewritePath() メソッドを使用して実行できます。HttpContext クラスには、特定の HTTP 要求に関する HTTP 固有の情報が格納されています。ASP.NET エンジンで個々の要求を受け取ると、その要求に対して HttpContext インスタンスが作成されます。このクラスには Request、Response、Application、Session、User のようなプロパティがあります。Request と Response は、受信する要求と送信する応答へのアクセスを提供するプロパティであり、Application と Session は、アプリケーションとセッションの変数へのアクセスを提供するプロパティです。また、User は、認証されたユーザーに関する情報を提供するプロパティです。この他にも関連するプロパティがあります。

Microsoft .NET Framework Version 1.0 の RewritePath() メソッドでは、使用する新しいパスを単一の文字列として受け取ります。内部的には、HttpContext クラスの RewritePath(string) メソッドにより、Request オブジェクトの Path プロパティと QueryString プロパティが更新されます。.NET Framework Version 1.1 には、RewritePath(string) メソッドの他に、RewritePath() メソッドという別のフォームがあり、このメソッドでは 3 つの文字列入力パラメータを受け取ります。このオーバーロードされた代替フォームでは、Request オブジェクトの Path プロパティと QueryString プロパティを設定するだけでなく、Request オブジェクトの PhysicalPath、PathInfo、および FilePath の各プロパティに対する値を計算する際に使用される内部メンバ変数も設定します。

ASP.NET で URL 書き換えを実装するには、次の処理を行う HTTP モジュールまたは HTTP ハンドラを作成する必要があります。

  1. 要求されたパスを調べて、URL の書き換えが必要であるかどうかを決定する。
  2. URL の書き換えが必要な場合には、RewritePath() メソッドを呼び出してパスを書き換える。

たとえば、自社の Web サイトに、/info/employee.aspx?empID=employeeID と指定してアクセスする社員の個人情報ページがあるとします。URL をより "短縮可能" にするために、/people/EmployeeName.aspx と指定すればこのページにアクセスできるようにすることにしました。この場合に、URL 書き換えを使用する必要があります。というのは、/people/ScottMitchell.aspx というページが要求されたときに、/info/employee.aspx?empID=1001 というページが代わりに表示されるように URL を書き換える必要があるからです。

HTTP モジュールを使用した URL 書き換え

ASP.NET レベルで URL 書き換えを実行するときには、HTTP モジュールまたは HTTP ハンドラのいずれかを使用すると書き換えを実行できます。HTTP モジュールを使用する場合、URL を書き換える必要があるかどうかを、要求を処理するどの時点で調べるのか決定する必要があります。一見したところ、任意の選択に見えるかもしれませんが、この決定が重大な点と微妙な点の両方においてアプリケーションに影響を与える可能性があります。組み込み ASP.NET HTTP モジュールは書き換えの実行に Request オブジェクトのプロパティを使用しているため、書き換えを実行する時点の選択は重要な意味を持ちます。パスを書き換えると、Request オブジェクトのプロパティ値が変更されることを思い出してください。このような密接な関係がある組み込み HTTP モジュールとそれに関係するイベントを次に示します。

HTTP モジュール イベント 説明
FormsAuthenticationModule AuthenticateRequest フォーム認証を使用してユーザーが認証されているかどうかを決定します。認証されていない場合、自動的にユーザーは指定したログオン ページにリダイレクトされます。
FileAuthorizationMoudle AuthorizeRequest Windows 認証を使用しているときに、この HTTP モジュールが Microsoft Windows® アカウントに要求されたリソースへの適切なアクセス権があることを確認します。
UrlAuthorizationModule AuthorizeRequest 要求者が指定した URL にアクセスできることを確認します。URL 承認は、Web.config ファイルの

イベントは、BeginRequest イベント、AuthenticateRequest イベント、AuthorizeRequest イベントの順に発生します。

URL 書き換えを実行できる安全な場所の 1 つは、BeginRequest イベント内です。つまり、URL を書き換える必要があった場合には、組み込み HTTP モジュールが実行されるまでに URL の書き換えが完了することになります。この方法には、フォーム認証を使用している場合に弱点があります。以前にフォーム認証を使用したことがある場合は、ユーザーが制限されているリソースにアクセスしたときに、自動的に指定したログイン ページにリダイレクトされることをご存知でしょう。ログインに成功した後で、最初にアクセスを試みたページが再表示されます。

URL 書き換えが BeginRequest イベントまたは AuthenticateRequest イベントで実行されると、ユーザーは、要求の送信時にログイン ページから書き換えられたページにリダイレクトされます。ユーザーがブラウザのウィンドウに「/people/ScottMitchell.aspx」と入力して、これが /info/employee.aspx?empID=1001 に書き換えられるとします。フォーム認証を使用するように Web アプリケーションが構成されていた場合には、ユーザーが /people/ScottMitchell.aspx にアクセスしたときに URL が /info/employee.aspx?empID=1001 に書き換えられ、その次に FormsAuthenticationModule が実行されて、必要に応じてユーザーがログイン ページにリダイレクトされることになります。ただし、ログインに成功したときにユーザーに送信される URL は /info/employee.aspx?empID=1001 です。この URL が、FormsAuthenticationModule が実行されたときの要求の URL であったからです。

同様に、BeginRequest イベントまたは AuthenticateRequest イベントで URL 書き換えを実行すると、UrlAuthorizationModule で調べる URL は書き換えられた URL になります。Web.config ファイルの <location> 要素を使用して、特定の URL に対する承認を指定する場合には、書き換えられた URL を参照しなければならないことになります。

こういった微妙な違いを修正するために、AuthorizeRequest イベントで URL 書き換えを実行することにするかもしれません。この方法で URL 承認とフォーム認証のずれは修正されますが、ファイル承認が機能しないという新たな問題が発生します。Windows 認証を使用しているときには、FileAuthorizationModule で、認証されたユーザーが特定の ASP.NET ページに対する適切なアクセス権があるかどうかの確認が行われます。

あるユーザー グループには、C:\Inetput\wwwroot\info\employee.aspx にアクセスする Windows レベルでのファイル アクセス権がない場合を考えてください。このようなユーザーが /info/employee.aspx?empID=1001 にアクセスしようとすると、承認エラーとなります。しかし、URL 書き込みを AuthenticateRequest イベントに移行すると、FileAuthorizationModule がセキュリティ設定の確認を行うときには、URL がまだ書き換えられていないので、要求されているファイルは /people/ScottMitchell.aspx であると見なされます。その結果、ファイル承認チェックにパスして、書き換えられた URL /info/employee.aspx?empID=1001 のコンテンツがこのユーザーに表示されてしまいます。

それでは、HTTP モジュールの場合、どの時点で URL 書き換えを実行すべきなのでしょうか。それは、採用している認証の種類によって異なります。何も認証を使用していない場合には、URL 書き換えを BeginRequest、AuthenticateRequest、あるいは AuthorizeRequest イベントで実行して構いません。フォーム認証を使用し、Windows 認証を使用していない場合には、AuthorizeRequest イベント ハンドラ内に URL 書き換えを設定してください。そして、Windows 認証を使用している場合には、URL 書き換えを BeginRequest イベントまたは AuthenticateRequest イベントで実行してください。

HTTP ハンドラでの URL 書き換え

URL 書き換えは、HTTP ハンドラや HTTP ハンドラ ファクトリを使用しても実行できます。HTTP ハンドラは、特定の種類の要求のコンテンツを生成するためのクラスで、HTTP ハンドラ ファクトリは、特定の種類の要求のコンテンツを生成する HTTP ハンドラのインスタンスを返すクラスです。

この記事では、ASP.NET Web ページ用の URL 書き換え HTTP ハンドラ ファクトリの作成について説明します。HTTP ハンドラ ファクトリは、GetHandler() メソッドを含む IHttpHandlerFactory インターフェイスを実装していなければなりません。該当する HTTP モジュールの初期化後、ASP.NET エンジンは、所定の要求に対してどの HTTP ハンドラまたは HTTP ハンドラ ファクトリを起動するかを決定します。HTTP ハンドラ ファクトリを起動する場合、ASP.NET エンジンは、その HTTP ハンドラ ファクトリの GetHandler() メソッドを呼び出して、その他の情報と共に Web 要求の HttpContext に渡します。そのとき、HTTP ハンドラ ファクトリは、要求を処理する IHttpHandler を実装するオブジェクトを返す必要があります。

HTTP ハンドラで URL 書き換えを実行する場合には、HTTP ハンドラ ファクトリを作成して、その GetHandler() メソッドで、要求されたパスを調べて書き換えが必要であるかどうかを決定します。書き換えが必要であれば、既に説明したように、HTTP ハンドラ ファクトリは渡された HttpContext オブジェクトの RewritePath() メソッドを呼び出します。最後に、HTTP ハンドラ ファクトリは、System.Web.UI.PageParser クラスの GetCompiledPageInstance() メソッドから返された HTTP ハンドラを返します。これは、ASP.NET Web ページの組み込み HTTP ハンドラ ファクトリ PageHandlerFactory が実行する技法と同じです。

カスタム HTTP ハンドラ ファクトリがインスタンス化される前に、HTTP モジュールはすべて初期化されているので、HTTP ハンドラ ファクトリを使用すると、イベントの後半段階に URL 書き換えを設定する場合と同じ問題が生じます。すなわち、ファイル承認は実行されません。そこで、Windows 認証とファイル承認を使用する場合は、HTTP モジュールによる方法を使用して、URL 書き換えを行う必要があります。

次のセクションでは、再利用可能な URL 書き換えエンジンの作成について説明します。この記事のダウンロード コードで提供されている URL 書き換えエンジンを説明してから、残りの 2 つのセクションでは、実際に URL 書き換えを使用する事例について説明します。まず URL 書き換えエンジンの使用方法を説明して、簡単な URL 書き換えの一例を見ていきます。その後、本当に "短縮可能" な URL を提供する、書き換えエンジンの正規表現機能を利用してみます。

URL 書き換えエンジンを作成する

ASP.NET Web アプリケーションでの URL 書き換えの実装方法を説明するために、私は URL 書き換えエンジンを作成しました。この書き換えエンジンは、次の機能を提供します。

  • URL 書き換えエンジンを利用する ASP.NET ページ開発者が、Web.config ファイルで書き換えルールを指定できる。
  • 書き換えルールでは、強力な書き換えルールの実行が可能な正規表現を使用できる。
  • URL 書き換えを、HTTP モジュールや HTTP ハンドラを使用する構成に簡単に設定できる。

この記事では、HTTP モジュールを使用した URL 書き換えを説明します。HTTP ハンドラを使用して URL 書き換えを実行する方法については、この記事でダウンロード可能なコードを参照してください。

URL 書き換えエンジンの構成情報を指定する

Web.config ファイルの書き換えルールの構造を調べてみましょう。HTTP モジュールや HTTP ハンドラを使用して URL 書き換えを実行するときには、まず Web.config ファイルで指定する必要があります。ダウンロードした Web.config ファイルには、コメント アウトされた次の 2 つのエントリが含まれています。

<!--
<httpModules>
<add type="URLRewriter.ModuleRewriter, URLRewriter" 
name="ModuleRewriter" />
</httpModules>
-->

<!--
<httpHandlers>
<add verb="*" path="*.aspx" 
type="URLRewriter.RewriterFactoryHandler, URLRewriter" />
</httpHandlers>
-->

HTTP モジュールを使用して書き換えるときは <httpModules> エントリをコメント アウトし、代わりに HTTP ハンドラを使用して書き換えるときは <httpHandlers> エントリをコメント アウトしてください。

書き換えに HTTP モジュールと HTTP ハンドラのどちらを使用するかを指定すると共に、Web.config ファイルに書き換えルールを含めます。書き換えルールは、要求された URL で検索するパターンと、検索された場合にそのパターンと置き換える文字列の 2 つの文字列からなります。この情報は、次の構文を使用して Web.config ファイルに記述します。

<RewriterConfig>
<Rules>
<RewriterRule>
<LookFor>検索するパターン</LookFor>
<SendTo>パターンと置き換える文字列</SendTo>
</RewriterRule>
<RewriterRule>
<LookFor>検索するパターン</LookFor>
<SendTo>パターンと置き換える文字列</SendTo>
</RewriterRule>
   ...
</Rules>
</RewriterConfig>

各書き換えルールは <RewriterRule> 要素で記述します。検索するパターンを <LookFor> 要素で指定し、検索されたパターンと置き換える文字列は <SentTo> 要素に入力します。これらの書き換えルールは、上から下に順に評価されます。一致する文字列が見つかると、URL は書き換えられて、書き換えルール検索は終了します。

<LookFor> 要素でパターンを指定すると、正規表現を使用して照合と文字列置換が行われます。正規表現を使用したパターンの検索方法を説明する実際の例については、後ほど少しだけ説明する予定です。なお、パターンは正規表現であるので、正規表現で予約されている文字は必ずすべてエスケープしてください。正規表現の予約文字には、.、?、^、$ などがあります。これらの予約文字は、文字の前にバックスラッシュ (\、日本語システムでは円記号 (\) として表示されます) を付けるとエスケープできます。たとえば "\." は文字どおりのピリオドと同じになります。

HTTP モジュールを使用した URL 書き換え

HTTP モジュールの作成は、IHttpModule インターフェイスを実装するクラスを作成するのと同様に簡単です。IHttpModule インターフェイスでは、次の 2 つのメソッドを定義します。

  • Init(HttpApplication): HTTP モジュールの初期化時に、このメソッドが呼び出されます。このメソッドで、イベント ハンドラを対応する HttpApplication イベントに割り当てます。
  • Dispose(): 要求が実行されて IIS に送り返されたときに、このメソッドが呼び出されます。このメソッドで、最終的なクリーンアップをすべて実行する必要があります。

URL 書き換え用の HTTP モジュールを簡単に作成するために、私は抽象型基本クラス、BaseModuleRewriter の作成から開始しました。このクラスで IHttpModule を実装しています。Init() イベントでは、HttpApplication の AuthorizeRequest イベントを BaseModuleRewriter_AuthorizeRequest メソッドに割り当てます。BaseModuleRewriter_AuthorizeRequest メソッドがこのクラスの Rewrite() メソッドを呼び出して、要求された Path オブジェクトを、Init() メソッドに渡された HttpApplication オブジェクトと共に渡します。この Rewrite() メソッドは抽象メソッドです。つまり、BaseModuleRewriter クラスの Rewrite() メソッドにはメソッド本体がありません。むしろ、BaseModuleRewriter の派生クラスでこのメソッドをオーバーライドして、メソッド本体を提供しなければなりません。

この基本クラスを設定したら、後は Rewrite() メソッドをオーバーライドする BaseModuleRewriter の派生クラスを作成して、そこで URL 書き換えロジックを実行するだけです。BaseModuleRewriter のコードを次に示します。

public abstract class BaseModuleRewriter :IHttpModule
{
public virtual void Init(HttpApplication app)
   {
      // 注意! これは Windows 認証では動作しません。
      // Windows 認証を使用している場合は、
      // app.BeginRequest に変更してください。
      app.AuthorizeRequest += new 
         EventHandler(this.BaseModuleRewriter_AuthorizeRequest);
   }

   public virtual void Dispose() {}

   protected virtual void BaseModuleRewriter_AuthorizeRequest(
     object sender, EventArgs e)
   {
      HttpApplication app = (HttpApplication) sender;
      Rewrite(app.Request.Path, app);
   }

   protected abstract void Rewrite(string requestedPath, 
     HttpApplication app);
}

BaseModuleRewriter クラスが、AuthorizeRequest イベントで URL 書き換えを実行することに注意してください。Windows 認証とファイル承認を使用している場合は、URL 書き換えが BeginRequest イベントまたは AuthenticateRequest イベントのいずれかで実行されるように、BaseModuleRewriter を変更する必要があります。

ModuleRewriter クラスは BaseModuleRewriter クラスを拡張して、実際の URL 書き換えを実行するクラスです。この ModuleRewriter クラスに、オーバーライドされた単一の Rewrite() メソッドが含まれます。このメソッドを次に示します。

protected override void Rewrite(string requestedPath, 
   System.Web.HttpApplication app)
{
   // 構成ルールを取得します。
   RewriterRuleCollection rules = 
     RewriterConfiguration.GetConfig().Rules;

   // 各ルールを繰り返し処理します。
   for(int i = 0; i < rules.Count; i++)
   {
      // 検索するパターンを取得し、
      // URL を解決します (~ を適切なディレクトリに変換します)。
      string lookFor = "^" + 
        RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, 
        rules[i].LookFor) + "$";

      // regex を作成します (IgnoreCase が設定されていることに注意)。
      Regex re = new Regex(lookFor, RegexOptions.IgnoreCase);

      // 一致する文字列が見つかったかどうかを調べます。
      if (re.IsMatch(requestedPath))
      {
         // 一致する文字列が見つかったら、必要な置換を実行します。
         string sendToUrl = 
RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, 
            re.Replace(requestedPath, rules[i].SendTo));

         // URL を書き換えます。
         RewriterUtils.RewriteUrl(app.Context, sendToUrl);
         break;      // for ループを終了します。
      }
   }
}

Rewrite() メソッドは、Web.config ファイルから書き換えルール セットを取得する処理から開始します。次に、1 回に 1 つずつの書き換えルール処理を繰り返し行って、ルールごとに LookFor プロパティを取得し、正規表現を使用して、要求された URL の中に一致する文字列が見つかったかどうかを判断します。

一致する文字列が見つかった場合は、要求されたパスで SendTo プロパティの値との正規表現による置き換えを行います。そして、この置き換えられた URL を RewriterUtils.RewriteUrl() メソッドに渡します。RewriterUtils は、URL 書き換え用の HTTP モジュールと HTTP ハンドラの双方で使用される、静的メソッドを提供するヘルパー クラスです。RewriterUrl() メソッドは、HttpContext オブジェクトの RewriteUrl() メソッドを呼び出しているだけです。

注: 正規表現による照合と置換を行うときに、RewriterUtils.ResolveUrl() への呼び出しが行われる場合があります。このヘルパー メソッドは、文字列内の "~" インスタンスをアプリケーションのパスの値に置き換えているだけです。

URL 書き換えエンジンの全コードは、この記事からダウンロードできます。最も密接に関連するコードについて説明しましたが、URL 書き換え用の HTTP ハンドラ ファクトリだけでなく、Web.config ファイル内の XML でフォーマットされた書き換えルールをオブジェクトに逆シリアル化するクラスなど、その他のコンポーネントもあります。この記事の残りの 3 つのセクションでは、実際に URL 書き換えを使用する事例について説明します。

URL 書き換えエンジンを使用して簡単な URL 書き換えを実行する

URL 書き換えエンジンの動作を説明するために、簡単な URL 書き換えを使用する ASP.NET Web アプリケーションを作成しましょう。そこで、詰め合わせ製品をオンライン販売する会社に勤務していると考えてください。取り扱い製品は、次のカテゴリに分類されます。

カテゴリ ID カテゴリ名
1 飲料
2 香辛料
3 菓子類
4 乳製品
... ...

既に ListProductsByCategory.aspx という ASP.NET Web ページが作成されていて、そのページでは、クエリ文字列の Category ID の値を受け取って、そのカテゴリに属する全製品を表示すると仮定します。そして、飲料製品を表示するユーザーは ListProductsByCategory.aspx?CategoryID=1 にアクセスし、乳製品を表示するユーザーは ListProductsByCategory.aspx?CategoryID=4 にアクセスするとします。また、販売している製品のカテゴリ一覧を表示する、ListCategories.aspx というページも作成済みであるとします。

ユーザーにとって表示される URL は何の意味もなく、またこの URL では "短縮性 (hackability)" も提供されないので、明らかにこれは URL 書き換えを行うケースと言えます。そこで、URL 書き換えを使用して、ユーザーが /Products/Beverages.aspx にアクセスしたら、その URL が ListProductsByCategory.aspx?CategoryID=1 に書き換えられるようにしましょう。Web.config ファイルで次の URL 書き換えルールを指定すると、この URL 書き換えを実行できます。

<RewriterConfig>
   <Rules>
<!-- 製品を一覧表示するためのルール -->
      <RewriterRule>
         <LookFor>~/Products/Beverages\.aspx</LookFor>
         <SendTo>~/ListProductsByCategory.aspx?CategoryID=1</SendTo>
      </RewriterRule>
      <RewriterRule>
   </Rules>
</RewriterConfig>

おわかりのように、このルールでは、ユーザーから要求されたパスが /Products/Beverages.aspx であるかどうかを調べ、そうであった場合には、その URL を /ListProductsByCategory.aspx?CategoryID=1 に書き換えています。

注: <LookFor> 要素で Beverages.aspx のピリオドがエスケープされていることに注目してください。<LookFor> の値は正規表現パターンに使用されます。ところが、ピリオドは正規表現では "任意の 1 文字に一致する" ことを意味する特殊文字なので、たとえば /Products/BeveragesQaspx という URL も一致することになります。そこで、"\." を使用してピリオドをエスケープすることにより、すべての文字ではなく、文字どおりのピリオドと照合するよう指定しています。

このルールを設定すると、ユーザーが /Products/Beverages.aspx にアクセスしたときに、販売中の飲料が表示されます。図 3 は、/Products/Beverages.aspx にアクセスしたときのブラウザのスクリーンショットです。ブラウザのアドレス バーには /Products/Beverages.aspx という URL が表示されていますが、ユーザーに実際に表示されるのは ListProductsByCategory.aspx?CategoryID=1 のコンテンツです。事実、Web サーバーには /Products/Beverages.aspx ファイルは存在すらしていません。

図 3. URL 書き換え後のカテゴリ一覧を要求する

次に、/Products/Beverages.aspx と同様に、他の製品カテゴリの書き換えルールも追加します。これは、Web.config ファイルの <Rules> 要素内に <RewriterRule> 要素を追加するだけで簡単に実行できます。このデモの完全な書き換えルールについては、ダウンロードした Web.config ファイルを参照してください。

URL をより "短縮可能" にするために、ユーザーが /Products/Beverages.aspx から Beverages.aspx を省略しても、製品カテゴリの一覧が表示されるようにすると良いでしょう。一見すると、これは /Products/ を /ListCategories.aspx にマップする書き換えルールを追加するだけの、取るに足らないタスクのように見えるかもしれません。しかし、微妙な違いがあります。まず /Products/ ディレクトリを作成してから、この /Products/ ディレクトリに空の Default.aspx ファイルを追加する必要があるのです。

なぜこういった特別な手順を実行する必要があるのかを理解するには、URL 書き換えエンジンが ASP.NET レベルで動作することを思い出してください。つまり、ASP.NET エンジンに要求を処理する機会が与えられていないと、URL 書き換えエンジンでは受信する URL を調べる方法がありません。また、IIS で受信要求を ASP.NET エンジンに渡すのは、要求されているファイルに該当する拡張子が付いている場合だけであることも覚えておいてください。したがって、ユーザーが /Products/ にアクセスしたときには、ファイル拡張子がないため、IIS はディレクトリを調べて、既定のファイル名のいずれかを持つファイルが存在しているかどうかを確認します。ここで既定のファイル名とは、Default.aspx、Default.htm、Default.asp などです。こういった既定のファイル名は、[IISAdmin] の [<Web サーバー> のプロパティ] ダイアログ ボックスの [ドキュメント] タブで定義されています。もちろん、/Products/ ディレクトリが存在していない場合には、IIS から HTTP 404 エラーが返されます。

そこで、/Products/ ディレクトリを作成する必要があり、さらに、このディレクトリ内に Default.aspx という 1 つのファイルを作成する必要もあります。こうすると、ユーザーが /Products/ にアクセスしたときに、IIS がそのディレクトリを調べて、Default.aspx という名前のファイルが存在するかどうかを確認してから、ASP.NET エンジンに処理を渡してくれます。ようやくこれで、URL 書き換えエンジンが URL の書き換えを実行するチャンスが得られます。

こうして /Products/ ディレクトリと Default.aspx ファイルを作成したら、引き続き、次の書き換えルールを <Rules> 要素に追加してください。

<RewriterRule>
   <LookFor>~/Products/Default\.aspx</LookFor>
   <SendTo>~/ListCategories.aspx</SendTo>
</RewriterRule>

このルールを設定すると、ユーザーが /Products/ または /Products/Default.aspx にアクセスしたときに、図 4 に示す製品のカテゴリ一覧が表示されます。

図 4. URL に "短縮性" を追加する

ポストバックを処理する

書き換える URL にサーバー側 Web フォームが含まれていて、ポストバックが実行されるときには、フォームのポストバック時に、書き換え前の URL が使用されます。つまり、ユーザーが /Products/Beverages.aspx にアクセスすると、ブラウザのアドレス バーには /Products/Beverages.aspx と表示されますが、表示されるコンテンツは ListProductsByCategory.aspx?CategoryID=1 のコンテンツです。ListProductsByCategory.aspx でポストバックが実行されるときには、/Products/Beverages.aspx ではなく、ListProductsByCategory.aspx?CategoryID=1 がユーザーにポストバックされるのです。これによって処理が中断されたりはしませんが、ユーザーの側からすると、ボタンをクリックしたときに突然 URL が変更されるため、まごつくことがあるかもしれません。

この動作が行われるのは、Web フォームがレンダリングされるときに、明示的に action 属性を Request オブジェクトのファイル パスの値に設定するためです。もちろん、Web フォームがレンダリングされるまでには、URL は /Products/Beverages.aspx から ListProductsByCategory.aspx?CategoryID=1 に書き換えられているので、ユーザーが ListProductsByCategory.aspx?CategoryID=1 にアクセスしていることを Request オブジェクトでレポートしていることになります。この問題は、サーバー側フォームで action 属性がレンダリングされないようにするだけで修正できます (既定では、フォームに action 属性が含まれていないときにブラウザがポストバックを行うためです)。

残念ながら、Web フォームでは、明示的に action 属性を指定することも、action 属性のレンダリングを無効にするプロパティを設定することもできません。むしろ、System.Web.HtmlControls.HtmlForm クラスそのものを拡張して、RenderAttribute() メソッドをオーバーライドして、明示的に action 属性をレンダリングしないように指示する必要があります。

継承という機能のおかげで HtmlForm クラスの機能をすべて取得できるため、わずか数行のコードを追加するだけで、この目的の動作を実現できます。このカスタム クラスの完全なコードを次に示します。

namespace ActionlessForm {
  public class Form : System.Web.UI.HtmlControls.HtmlForm
  {
     protected override void RenderAttributes(HtmlTextWriter writer)
     {
        writer.WriteAttribute("name", this.Name);
        base.Attributes.Remove("name");

        writer.WriteAttribute("method", this.Method);
        base.Attributes.Remove("method");

        this.Attributes.Render(writer);

        base.Attributes.Remove("action");

        if (base.ID != null)
           writer.WriteAttribute("id", base.ClientID);
     }
  }
}

オーバーライドされた RenderAttributes() メソッドのコードには、HtmlForm クラスの RenderAttributes() メソッドと同じコードが含まれていますが、action 属性は設定されていません (私は HtmlForm クラスのソース コードを表示するために、Lutz Roeder の Reflector Non-MS link (英語) を使用しました)。

このクラスを作成してコンパイルしたら、ASP.NET Web アプリケーションでこのクラスを使用するときに、Web アプリケーションの References フォルダに追加して起動してください。そして、このクラスを HtmlForm クラスの代わりに使用するときは、ASP.NET Web ページの先頭に次のコードを追加してください。

<%@ Register TagPrefix="skm" Namespace="ActionlessForm" 
   Assembly="ActionlessForm" %>

<form runat="server"> が含まれている場合は、これを次のコードに置き換えます。

<skm:Form id="Form1" method="post" runat="server">

さらに、終了タグ </form> を次のタグに置き換えます。

</skm:Form>

この記事のダウンロード ファイルに含まれている ListProductsByCategory.aspx で、このカスタムの Web フォーム クラスの動作を確認できます。また、ダウンロード ファイルには、action 属性が設定されていない Web フォームの Visual Studio .NET プロジェクトも含まれています。

注: 書き換え先の URL でポストバックが実行されない場合には、このカスタム Web フォーム クラスを使用する必要はありません。

実際に "短縮可能" な URL を作成する

前のセクションの簡単な URL 書き換えでは、URL 書き換えエンジンを新しい書き換えルールで簡単に構成する方法を説明しました。しかし、書き換えルールの真の能力は、正規表現を使用したときに発揮されます。このセクションではこの点について説明します。

最近では blog がますます普及してきており、"誰も" が独自の blog を持っているようです。blog についてご存知でない場合には、通常オンライン ジャーナルとして機能する、頻繁に更新される個人ページと言ったらおわかりになるでしょう。大部分の blog 作成者は、単に日々の出来事について書き込みをしているだけですが、映画のレビュー、スポーツ チーム、コンピュータ テクノロジなどの特定のテーマに関する blog を中心に扱っている人もいます。

blog の更新も、作者によって 1 日に数回から、1 ~ 2 週間に 1 回程度までさまざまです。通常 blog ホームページには 最新の 10 項目が表示されますが、実際はすべての blog 作成ソフトウェアでは、サイト利用者が以前の投稿を読めるようにアーカイブを提供しています。blog は、"短縮可能" な URL 用の優れたアプリケーションなのです。blog のアーカイブを検索しているときに、URL に /2004/02/14.aspx と表示された場合を想像してください。このとき、2004 年 2 月 14 日の投稿を読んでいるとしたら、とても驚くのではないでしょうか。さらに、2004 年 2 月の投稿をすべて表示する必要があるときには、URL を /2004/02/ と末尾を省略することができ、2004 年の投稿をすべて表示するときには、/2004/ にアクセスできたらどうでしょう。

blog を保守管理するときに、このレベルの URL の "短縮性" をサイト利用者に提供できればすばらしいことです。多くの blog エンジンでこの機能を提供していますが、URL 書き換えを使用してこの機能を実現する方法を見てみましょう。

まず、年月日で blog エントリを表示する単一の ASP.NET Web ページが必要です。クエリ文字列パラメータ year、month、day を取り込む、この ASP.NET Web ページ ShowBlogContent.aspx が既にあると仮定します。2004 年 2 月 14 日の投稿を表示するときは ShowBlogContent.aspx?year=2004&month=2&day=14 に、2004 年 2 月の投稿をすべて表示するときは ShowBlogContent.aspx?year=2004&month=2 に、2004 年の投稿をすべて表示するときは ShowBlogContent.aspx?year=2004 にアクセスすることになります (ShowBlogContent.aspx のコードは、この記事のダウンロード ファイル内にあります)。

それで、ユーザーが /2004/02/14.aspx にアクセスしたときに、その URL を ShowBlogContent.aspx?year=2004&month=2&day=14 に書き換えなければなりません。URL が 年月日で指定された場合、年と月で指定された場合、そして年だけで指定された場合の 3 つのケースすべてが、次の 3 つの書き換えルールで処理されます。

<RewriterConfig>
   <Rules>
      <!-- Rules for Blog Content Displayer -->
      <RewriterRule>
         <LookFor>~/(\d{4})/(\d{2})/(\d{2})\.aspx</LookFor>
         <SendTo>~/ShowBlogContent.aspx?year=$1&month=$2&day=$3</SendTo>
      </RewriterRule>
      <RewriterRule>
         <LookFor>~/(\d{4})/(\d{2})/Default\.aspx</LookFor>
         <SendTo><![CDATA[~/ShowBlogContent.aspx?year=$1&month=$2]]></SendTo>
      </RewriterRule>
      <RewriterRule>
         <LookFor>~/(\d{4})/Default\.aspx</LookFor>
         <SendTo>~/ShowBlogContent.aspx?year=$1</SendTo>
      </RewriterRule>
   </Rules>
</RewriterConfig>

これらの書き換えルールは、正規表現の能力を実証します。最初のルールでは、パターン (\d{4})/(\d{2})/(\d{2})\.aspx を使用して URL を検索します。これは、4 桁の数字、スラッシュ、2 桁の数字、スラッシュ、2 桁の数字、.aspx の順に並んでいる文字列と一致します。各桁数を囲むかっこは不可欠です。これにより、対応する <SendTo> プロパティのかっこ内で一致した文字を参照できるようになります。特に、最初から 3 番目までのかっこグループの代わりにそれぞれ $1、$2、$3 を使用すると、一致したかっこ内のグループを逆に参照することができます。

注: Web.config ファイルは XML でフォーマットされているので、要素のテキスト部分の &、<、および > などの文字をエスケープする必要があります。最初のルールの <SendTo> 要素に含まれる & は &amp; にエスケープされています。2 つ目のルールの <SendTo> では、別の技法が使用されています。<![CDATA[...]]> 要素を使用すると、この [...] 内のコンテンツをエスケープする必要はなくなります。いずれの方法も使用することができ、また同じ結果が得られます。

図 5、6、および 7 は、URL 書き換えが実行された結果を示しています。表示されているデータは、私の blog である http://scottonwriting.net/ Non-MS link (英語) から取り出した実際のデータです。図 5 には 2003 年 11 月 7 日の投稿が、図 6 には 2003 年 11 月のすべての投稿が、図 7 には 2003 年のすべての投稿が表示されています。

図 5. 2003 年 11 月 7 日の投稿

図 6. 2003 年 11 月のすべての投稿

図 7. 2003 年のすべての投稿

注: URL 書き換えエンジンは、<LookFor> 要素の正規表現パターンを予測します。正規表現に詳しくない場合は、私の以前の記事「An Introduction to Regular Expressions Non-MS link (英語)」をお読みください。また、最も一般的に使用される正規表現について学習する最適なサイトであるだけでなく、独自に作成した正規表現を共有するためのリポジトリでもあるのは、RegExLib.com Non-MS link (英語) です。

必要なディレクトリ構造を構築する

/2004/03/19.aspx に対する要求を受信すると、IIS では .aspx 拡張子を調べて、要求を ASP.NET エンジンにルーティングします。要求が ASP.NET エンジンのパイプラインを通って移動するにつれて、URL は ShowBlogContent.aspx?year=2004&month=03&day=19 に書き換えられて、2004 年 3 月 19 日の blog エントリがサイト利用者に表示されます。しかし、ユーザーが /2004/03/ に移動するときに、どのような処理が行われているのでしょうか。/2004/03/ というディレクトリが存在しない場合、404 エラーが IIS から返されます。また、要求が ASP.NET エンジンに渡されるようにするには、このディレクトリ内に Default.aspx ページが存在していなければなりません。

この方法では、blog エントリが含まれるディレクトリを年ごとに手動で作成して、そのディレクトリに Default.aspx ページを用意します。さらに、各年のディレクトリに、01、02、…、12 といった 12 個のディレクトリをさらに手動で作成して、各ディレクトリに Default.aspx ファイルを含めます。/Products/ にアクセスすると正しく ListCategories.aspx が表示されるように、前述のデモで行ったのと同様に、/Products/ ディレクトリに Default.aspx ファイルを追加する必要があります。

確かに、こういったディレクトリ構造を追加するのは苦痛であるかもしれません。この問題の回避策として、受信する IIS 要求をすべて ASP.NET エンジンにマップさせる方法があります。この方法により、/2004/03/ という URL にアクセスしたときに、/2004/03/ ディレクトリが存在しなくても、IIS は確実に要求を ASP.NET エンジンに渡します。ただし、この方法を使用すると、イメージ、CSS ファイル、外部 JavaScript ファイル、Macromedia Flash ファイルなど、Web サーバーに入ってくるあらゆる種類の要求を、ASP.NET エンジンで処理させることになります。

すべてのファイルの種類を処理する方法については、この記事では扱いません。この技法を使用している ASP.NET Web アプリケーションの例については、オープン ソースの blog エンジンである .Text (英語) を参照してください。すべての要求が ASP.NET エンジンにマップされるように、.Text を構成することができます。.Text では、通常の静的なファイルの種類 (イメージ、CSS ファイルなど) の処理方法を認識しているカスタム HTTP ハンドラを使用すると、すべてのファイルの種類を処理できます。

まとめ

この記事では、HttpContext クラスの RewriteUrl() メソッドを使用して、ASP.NET レベルで URL 書き換えを実行する方法を説明しました。既に見てきたように、RewriteUrl() メソッドは特定の HttpContext の Request プロパティを更新して、要求されているファイルとパスを更新します。ユーザーから見ると、特定の URL にアクセスしているとしても、Web サーバー側からは、実際にはそれとは異なる URL が要求されていることになります。

URL は、HTTP モジュールと HTTP ハンドラのいずれでも書き換えることができます。この記事では、HTTP モジュールを使用して書き換えを実行する方法を説明し、パイプラインのさまざまな段階で書き換えを実行した結果を調べました。

もちろん、ASP.NET レベルの書き換えでは、要求が正常に IIS から ASP.NET エンジンに渡された場合にのみ、URL 書き換えを実行できます。この URL 書き換えは、ユーザーが .aspx 拡張子が付いたページを要求したときに自然に行われます。ただし、実際には存在しない URL をユーザーが入力できるようにして、その URL を実在する ASP.NET ページに書き換えるようにする場合は、見せかけのディレクトリと Default.aspx ページを作成するか、または受信要求をすべてとりあえず ASP.NET エンジンにルーティングするように IIS を構成する必要があります。

関連書籍

ASP.NET:Tips, Tutorials, and Code (英語)

Microsoft ASP.NET Coding Strategies with the Microsoft ASP.NET Team (英語)

Essential ASP.NET with Examples in C# (英語)

参考資料

URL 書き換えは、ASP.NET だけでなく、これと競合するサーバー側 Web テクノロジにおいても注目されているトピックです。たとえば Apache Web サーバーでは、mod_rewrite Non-MS link (英語) という URL 書き換え用のモジュールを提供しています。mod_rewrite は、正規表現を使用する書き換えルールだけでなく、HTTP ヘッダーやサーバー変数などの条件に基づいた書き換えルールも提供する、堅牢な書き換えエンジンです。mod_rewrite の詳細については、「A User's Guide to URL Rewriting with the Apache Web Server Non-MS link (英語)」を参照してください。

ASP.NET を使用した URL 書き換えに関する記事は多数あります。「Rewrite.NET - A URL Rewriting Engine for .NET Non-MS link (英語)」では、mod_rewrite の正規表現ルールによく似た URL 書き換えエンジンの作成方法について説明されています。また、「URL Rewriting With ASP.NET Non-MS link (英語)」では、ASP.NET での URL 書き換え機能の概要が説明されています。Ian Griffiths 氏は、この記事で説明したポストバック問題など、ASP.NET での URL 書き換えに関連する注意事項に関する blog エントリ Non-MS link (英語) を公開しています。Fabrice Marguerie 氏 (詳細を表示) Non-MS link (英語) と Jason Salas 氏 (詳細を表示) Non-MS link (英語) も、URL 書き換えを使用して検索エンジンで上位に表示するための blog エントリを作成しています。

 

著者について

Scott Mitchell は 4GuysFromRolla.com の創設者であり、過去 5 年間にわたり Microsoft の Web テクノロジに取り組み、5 冊の書籍を著しています。現在、フリーランスのコンサルタント、トレーナー、およびライターとして活動しています。Scott のメール アドレスは mitchell@4guysfromrolla.com です。また、http://scottonwriting.net/ Non-MS link の彼の blog からもコンタクトできます。