ScottGu's blog translated by Chica @ Wankuma

ASP.NET MVC フレームワーク (パート 2): URL ルーティング

  

先月現在取りかかっている新しいASP.NET MVCフレームワークをカバーするブログ投稿のシリーズの1つ目を投稿しました。このシリーズの1つ目の投稿では簡単な電子取引での製品の一覧表示や検索サイトを構築しました。そこではMVCの背後にある高いレベルの概念をカバーしており、ASP.NET MVCプロジェクトを何も無いところから作成し、この電子取引製品の一覧表示機能を実装・テストする方法をご紹介しました。

本日のブログ投稿では、ASP.NET MVCフレームワークのURLルーティングアーキテクチャーについて深く掘り下げ、それをアプリケーションでより上級なシナリオで使用する方法をいくつかお話しいたします。

パート 1の簡単なレビュー

このシリーズのパート 1では、3つのタイプのURLを公開する電子取引サイトを作成しました。:

URL フォーマット 動作 URL の例
/Products/Categories すべての製品カテゴリを検索 /Products/Categories
/Products/List/Category カテゴリ内の製品を一覧表示 /Products/List/Beverages
/Products/Detail/ProductID 特定の製品ついて詳細を表示 /Products/Detail/34

これらのURLを以下のような"ProductsController"クラスを作成して処理しました。:

上記のクラスがアプリケーションに追加されると、ASP.NET MVC フレームワークは、コントローラ上で処理を行う適切なアクションメソッドへ自動的に入って来たURLをルーティング処理します。

本日のブログ投稿では、まさにこのURLマッピングがどの様に行われたのかを見ていくと同時にASP.NET MVC フレームワークで活用できるより上級のシナリオを見ていきたいと思います。また、URLルーティングのシナリオを簡単にテストする方法をご紹介します。

ASP.NET MVC URL ルーティングシステムは何を行っているのか?

ASP.NET MVCフレームワークには柔軟なURLルーティングシステムがあり、アプリケーションでURLマッピングルールを定義することができます。このルーティングシステムには主に2つの目的があります。:

  1. 入ってくるURLをアプリケーションにマップおよびそれらのルートの決定。それにより適切なコントローラとアクションメソッドがそれらの処理を実行します。
  2. コントローラ/アクション(例えば : フォームへのポスト、 <a href=""> リンク、AJAXのコール)へのコールバックに使用されるような外向きのURLを構築。

内向き・外向きのURLシナリオの両方を処理するURLマッピングルールを使用することができるようになると、アプリケーションコードに大きな柔軟性を与えることができます。アプリケーションのURL構造を後で変更したい場合(例えば : /Productsを/Catalogへ変更)など、アプリケーションレベルでマッピングルールの1式を修正することで行えるようになり、コントローラまたはビューのテンプレート内でどのコードを変更する必要がありません。

デフォルトのASP.NET MVC URL ルーティングルール

デフォルトではVisual Studioで " ASP.NET MVC Web アプリケーション" テンプレートを使用して新しいプロジェクトを作成する時、ASP.NETアプリケーションクラスをプロジェクトに追加します。これはGlobal.asaxのコードビハインド内で実装されます。:

ASP.NETアプリケーションクラスにより開発者はアプリケーションの開始・終了、およびグローバルエラー処理を行うことができるようになります。

デフォルトのASP.NET MVCプロジェクトのテンプレートは自動的にApplication_Startメソッドをクラスに追加し、2つのURLルーティングルールをそれと一緒に登録します。:

上記の最初のルーティングルールは、どのコントローラクラスを初期化し、どのアクションメソッドを起動させるのか(引き渡す必要のある引数とともに)を決定する時に、ASP.NET MVCフレームワークは"[controller]/[action]/[id]"のフォーマットを使用して、デフォルトでURLをコントローラーへマップするべきであることを示しています。

このデフォルトのルーティングルールにより、パート 1にあった電子取引検索サンプル/Products/Detail/3に対するURLのリクエストが自動的にProductsControllerクラス上でDetailメソッドを発生させ、IDメソッドの引数値として3に引き渡したのです。:

上記の2つめのルーティングルールは、アプリケーションで特別なケースであるルートの"Default.aspx"(これは、アプリケーションのルートURLに対するリクエストを処理する際に"/"の代わりにWebサーバから時々引き渡されます。)URLに追加されます。このルールではアプリケーションのルートの"/Default.aspx" または "/"のどちらに対するリクエストも "HomeController"クラス(Visual Studioが自動的に新しいアプリケーションを "ASP.NET MVC Web アプリケーション" テンプレートを使用して作成したときに追加します。)上の "Index()" アクションによって処理されるようにします。

ルートインスタンスの理解

ルーティングルールはルートインスタンスをSystem.Web.Mvc.RouteTableのRoutesコレクションへ追加すると登録されます。

Route クラスでマッピングルールの構成に使用する数多くのプロパティが定義されます。これらのプロパティは "通常" の.NET 2.0プロパティ設定を使用して設定できます。:

もしくは新しいオブジェクトイニシャライザ機能をVS 2008のC#およびVBコンパイラで活用してより簡潔にプロパティを設定することもできます。:

 

Routeクラス上の"Url"プロパティはUrlのマッチングルールを定義します。これはルートルールが特定の内向きリクエストに適用されるかどうかを評価するために使用されるものです。これはまた、引数に対するURLのトークン化の方法も定義します。URLで入れ替え可能な引数は[ParamName]シンタックスを使用して定義されます。後で見ていきますが、"よく知られる"引数名だけに限られることなく、URLで使用したい任意の引数を任意の数持つことができます。例えば、ブログ投稿に対して入ってきたURLをトークン化するために"/Blogs/[Username]/Archive/[Year]/[Month]/[Day]/[Title]"のUrlルールを使用し、自動的にMVCフレームワークにUserName、Year、Month、Day、Titleの引数をコントローラのアクションメソッドへパースして引き渡すようにすることができます。

Routeクラス上の"Defaults"プロパティには、入ってくるURLに特定の引数の値が1つも含まれていなかった場合に使用するデフォルト値のディクショナリを定義します。たとえば、上記のURLマッピング例では、2つのデフォルトのURL引数の値、"[action]" と "[id]"、に対して定義しています。 つまり、/Products/に対するURLがアプリケーションに受信された場合、ルーティングシステムはProductsController上で実行されるデフォルトのアクション名として"Index"を使用します。同じく、/Products/List/ が特定された場合、"ID"引数に対してNull文字列値が使用されます。

Routeクラス上の"RouteHandler"プロパティでは、URLがトークン化され使用する適切なルーティングルールが決定された後に、リクエストを処理するために使用するIRouteHandlerインスタンスを定義します。この余分なステップがある理由はURLルーティングシステムがMVCとMVCでないリクエストの両方に対して確実に使用できるようにしたいためです。このIRouteHandlerインターフェイスを持つということは、MVCではないリクエスト(例えば、標準のWebフォーム、Astoria RESTサポートなど)に対してもクリーンにそれを使用することができるということです。

またRouteクラスには"Validation"プロパティがあり、それはこの投稿のもう少しあとで見ていきます。このプロパティには、プレコンディションを特定することができます。これは特定のルーティングルールに合致させるために必要になります。例えば、特定のHTTPバーブ(RESTコマンドに簡単にマップすることができる)に対してのみ適用するルーティングルールを示したり、ルーティングルールの合致に対してフィルターをかけるために引数に正規表現を使用したりすることができます。

注: 最初の公開MVCプレビューでは、Routeクラスは拡張可能ではありません。(データクラスになります。)次のプレビューリリースに向けて、それを拡張可能して、開発者がさらにセマンティックスや機能をクリーンに追加するためにシナリオの特定のルートクラス(例えば:RestRouteサブクラス)を追加できるようにしたいと思っています。

ルートルールの評価

入ってくるURLをASP.NET MVC Webアプリケーションが受信した時、MVCフレームワークはRouteTable.Routes コレクションでルーティングルールを評価し、そのリクエストを処理する適切なコントローラを決定します。

MVCフレームワークは登録された順序でRouteTableルールを評価することで使用するコントローラを選択します。入ってくるURLは各ルートルールに合致しているかどうかチェックされます。もしルートルールが合致した場合は、そのルール(そしてその関連するRouteHandler)がそのリクエストを処理するのに使用されます。つまり、通常の場合は " 詳細" 順でルーティングルールを構造化しておくといいと思います

ルーティングシナリオ: カスタムの検索URL

いくつかカスタムのルーティングルールを実際のシナリオで使用してみましょう。これは電子取引サイトに検索機能を実装して行います。

まず新しいSearchController クラスをプロジェクトに追加します。:

その後、SearchControllerクラスで2つのアクションメソッドを定義します。Index()アクションメソッドはユーザが入力し検索対象を送信するに使用するTextBoxがある検索ページを表示させるために使用されます。Results()アクションはそこからフォーム送信を処理し、それをデータベースに対して検索し、その結果を表示するために使用されます。:

デフォルトの /[controller]/[action]/[id] URL ルートマッピングルールを使用して、以下のような "設定済み"のURLをSearchControllerアクションの起動に使用することができます。 actions:

シナリオ URL アクションメソッド
検索フォーム: /Search/ Index
検索結果: /Search/Results?query=Beverages 結果
  /Search/Results?query=ASP.NET 結果

Index()アクションメソッドに対してデフォルトでルートの/SearchURLがマップされている理由は、Visual Studioが新しいプロジェクトを作成し、( "Defaults" プロパティを通じて)コントローラ上でデフォルトアクションとして"Index"を設定した時に、デフォルトで/[controller]/[action]/[id] ルート定義が追加されたためです。:

URLが /Search/Results?query=Beverages などの場合は完璧に機能しますが、少し"きれいな"URLを検索結果に出したいと思いました。特に"Results"アクション名をURLから削除して、QueryString引数を使う代わりにURLの一部として検索クエリを引き渡したい場合があるかと思います。例えば:

シナリオ URL アクションメソッド
検索フォーム: /Search/ Index
検索結果: /Search/Beverages Results
  /Search/ASP.NET Results

以下のように、デフォルトの/[controller]/[action]/[id] ルールの前に 2つのカスタムURLルートマッピングルールを追加することで " きれいな" 検索結果を有効にすることができます。 :

最初の2つのルールでは/Search/ URLに対してコントローラやアクション引数を現在明示的に特定しています。"/Search"は常にSearchController上で"Index"アクションで処理されるべきであることを示しています。現在サブURL階層を持つURLは全て(/Search/Fooや、/Search/Barなど)、常にSearchController上で"Results"アクションで処理されています。

上記で、2つ目のルーティングルールは/Search/プリフィックスを超えるものは全て" [query]" と呼ばれる引数として取り扱われ、それはSearchController上のResultsアクションへメソッド引数として引き渡されることを示しています。:

ほとんどの場合(検索結果が10以上表示される場合)、検索結果をページ番号付したくなると思います。これはクエリストリングの引数を通じて行うか (例えば: /Search/Beverages?page=2)、またはオプションとしてURLの一部にページ番号を埋め込みます (例えば: /Search/Beverages/2)。これを後でオプションとしてサポートするために、追加の引数を2番目のルーティングルールに追加しておきます。:

上記でご確認頂ける様に、新しいURLルールの合致が現在 "Search/[query]/[page]" になっています。また、デフォルトページ番号はURLに含まれていない場合のために1に設定します。(これは、"Defaults"プロパティ値として引き渡された匿名型を通じて行われます。)

その後、SearchController.Resultsアクションメソッドを更新して、このページの引数をメソッドの引数として受け取ります。:

 

そうすると、 サイトに対して"きれいな URL" で検索が行えます。(後は検索アルゴリズを実装するだけです。これはリーダーにエクササイズとして置いておきます。 <g>)

ルーティングルールに対する前提コンディションの評価

この投稿の前の方でも述べましたが、Routeクラスは"Validation"プロパティを持っており、それによりルートルールが合致するにはTrueとなっていなければならない前提条件ルールの検証を追加することができます。ASP.NET MVCフレームワークは正規表現を使用してURLの各引数を評価をすることができ、またHTTPヘッダの評価を行う(HTTPバーブに応じて異なるURLのルーティングを行う)こともできます。

以下はカスタム評価ルールで、 "/Products/Detail/43"などのようなURLに対して有効化することができます。それは、ID引数は数値(文字列は不許可)で、1-8文字でなければならない設定にします。:

アプリケーションに /Products/Detail/12 のようなURLを引き渡した場合、上記のルーティングルールは有効になりますが、 /Products/Detail/abc or /Products/Detail/23232323232 だと合致しません。

ルーティングシステムから外向きURLを構築

このブログ投稿の前の方で述べましたがASP.NET MVCフレームワークのURLのルーティングシステムは2つのことを行っています。:

  1. 処理するために入って来たURLをControllers/Actionsへマッピング
  2. Controllers/Actionsに後でコールバックに使用する可能性のある外向きのURLの構築をサポート (例えば: フォームの投稿、<a href=""> リンク、AJAX コール)

URLのルーティングシステムには数多くのヘルパーメソッドやクラスがあり、それらにより簡単に実行時でのURLを動的に検索および構築することができます。(RouteTableのRouteコレクションを直接操作することでURLを検索することもできます。)

Html.ActionLink

このブログシリーズのパート1で簡単にHtml.ActionLink() ビューヘルパーメソッドについて述べましたが、それはビュー内で使用することができ、動的に <a href=""> ハイパーリンクを生成することができます。利点が何かというと、MVCルーティングシステムで定義されたURLマッピングルールを使用してこれらのURLを生成するところです。例えば、以下の2つのHtml.ActionLink コール:

自動的にこの投稿の前の方で構成した特定のSearch結果のルートルールと、これを反映して自動的に生成した"href" 属性を選択します。:

特に、どのようにHtml.ActionLinkに対する2つ目のコールが自動的に "page"引数をURLの一部としてマップされているか上記でご確認ください。(また、サーバーサイドでデフォルト値が提供されることが分かっているため、1つ目のコールでページの引数値が省かれている様子をご確認ください。)

Url.Action

Html.ActionLinkの使用に加え、ASP.NET MVC には Url.Action() ビューヘルパーメソッドもあります。これは生の文字列URLを生成します。これは使用したい方法でご利用いただけます。例えば、以下のコードスニペット:

URLルーティングシステムを使用して、以下の生のURLを返します。 (<a href=""> 要素にはラップされていない):

Controller.RedirectToAction

ASP.NET MVC は Controller.RedirectToAction() ヘルパーメソッドをサポートしており、それはコントローラ内で使用して、(URLがURLルーティングシステムを使用して計算された場合)リダイレクトを実行することができます。

例えば、以下のコードがコントローラ内で起動された時:

 

内部でResponse.Redirect("/Search/Beverages")へのコールを生成します。

DRY

上記の全てのヘルパーメソッドの美しさは、コントローラやビューのロジック内でURLパスにハードコードする必要が無いことです。もし後で、"/Search/[query]/[page]" から"/Search/Results/[query]/[page]" もしくは /Search/Results?query=[query]&page=[page]" へ検索URLのルートマッピングルールを変更しようと決めた場合、1つの場所(ルート登録コード)で編集することで簡単に行うことができます。 新しいURL (これは "DRY principle"を保持します。)を選択するために、ビューまたはコントローラ内でコードを変更する必要がありません。

ルーティングシステムから外向きのURLを構築(ラムダ式を使用して)

前のURLヘルパー例は、VS 2008でサポートされているVBとC#がサポートしている新しい 匿名型 の活用しています。 上記の例では匿名型を使用して効果的に一連の名前と値のペアを引渡しURLのマッピングを補助しています。(これを ディクショナリをきれいに生成する方法と考えることができます。)

匿名型を使用した動的な方法で引数を引き渡すことに加え、ASP.NET MVC フレームワークは強く型付けされたメカニズムを使用してアクションルートを作成する機能もサポートしており、それによりコンパイル時でのチェックとURLヘルパーに対するインテリセンスが提供されます。Generic型とラムダ式に対する新しいVBとC#サポートを使用してこれを行います。

例えば、以下の匿名型の ActionLinkコール:

また次のようにも書くことができます。:

少し簡潔に書けることに加え、この2つ目のオプションは型セーフである利点があります。つまり、式のコンパイル時チェックとVisual Studioコードインテリセンス(リファクタリングツールも使用することができます。)を行うことができます。 :

上記で、SearchController 上でアクションメソッドを選択するためにインテリセンスをどのように使用するのか、引数がどのように強く型付けされるのかをご確認ください。生成されたURLはすべてASP.NET MVC URL ルーティングシステムから無くなります。

これはどのように動くのか?と思われるかもしれません。8ヶ月前にラムダ式についてブログに投稿したのですが、そこでコードデリゲート、およびラムダ式を分析するために実行時に使用可能なエクスプレッションツリーオブジェクトのどちらかにラムダ式をコンパイルすることができる様子についてお話しました。Html.ActionLink<T>ヘルパーメソッドで、このエクスプレッションツリーオプションを使用し、実行時にラムダ式を分析してそれを起動させるアクションメソッド、そして式の中で特定されたその引数の型、名前、値を検索します。これらをMVC Url ルーティングシステムで使用して適切なURLと関連のHTMLを返すことができます。

重要: このラムダ式のアプローチを使用する時、コントローラアクションは全く実行しません。例えば、以下のコードはSearchController上で"Results" アクションメソッドを起動しません。:

このHTMLのハイパーリンクを単に返す代わりに:

エンドユーザがこのハイパーリンクをクリックした時に、HTTPリクエストがサーバーへ送信され、SearchControllerのResultsアクションメソッドを起動します。

ルートの単体テスト

ASP.NET MVC フレームワークのコア設計の原則の1つがテストを大いにサポートできるようにすることです。残りのMVCフレームワークの様に、簡単にルートやルートの合致ルールの単体テストを行うことができます。MVCのルーティングシステムはASP.NETから独立して初期化し起動することができます。つまり、どんな単体テストライブラリ(Webサーバを開始する必要はありません。)内でも、どんな単体テストフレームワーク(NUnit、 MBUnit、MSTestなど)を使用してもルートパターンをロードし単体テストすることができます。

複数の単体テストの中で、直接ASP.NET MVCアプリケーションのグローバルRouteTableマッピングコレクションを単体テストすることはできますが、一般的には単体テストを変更したりグローバルの状況に頼ることはいい考えではありません。いいパターンは以下のようにRegisterRoutes()ヘルパーメソッドにルート登録ロジックを構造化することです。これは引数として引き渡されるRouteCollectionに対して動作します。(注:恐らくこれを次のプレビューの更新でデフォルトのVSテンプレートパターンにすると思います。):

固有のRouteCollectionインスタンスを作成し、ルートルールをアプリケーションの中で登録するためにアプリケーションのRegisterRoutes()ヘルパーを呼び出す単体テストを書くことができます。そうすれば、アプリケーションに対するリクエストをシュミレートして、それらに対して正しいコントローラやアクションが登録されているか、何の副作用も心配することなく確かめることができます。 :

まとめ

今回の投稿によりASP.NET MVCルーティングアーキテクチャーの動作、またそれを使用してASP.NET MVCアプリケーション内で発行するURLの構造やレイアウトをカスタマイズする方法について、より詳細な情報がご提供できていれば幸いです。

デフォルトでは、新しくASP.NET MVC Webアプリケーションを作成した時、(手動での構成や有効化などすることなく)デフォルトの/[controller]/[action]/[id] ルーティングルールが前定義されます。これにより、自分で固有のカスタムでルーティングルールを登録する必要なく多くのアプリケーションを構築することができるようになりますが、上記でご紹介したものにより、URLフォーマットをカスタマイズしたい場合に簡単に作成でき、またこれを行う際にMVCフレームワークが大きなパワーと柔軟性を与えることに繋がればうれしく思います。

Hope this helps,

Scott

ScottGu's blog translated by Chica @ Wankuma 

※本翻訳に関しまして、Scottさんにはご了承頂いております。