ScottGu's blog translated by Chica @ Wankuma

ASP.NET MVC フレームワーク (パート 3): コントローラからビューへViewDataの引き渡し

  

先週から現在取りかかっている新しいASP.NET MVCフレームワークをカバーするブログ投稿のシリーズを行っています。ASP.NET MVC フレームワークはオプションとしてのアプローチで、ASP.NETのWebアプリケーションを構成する際に問題点の分別、コードの単体テスト、TDDワークフローのサポートを簡単に行えるようになります。

このシリーズの最初の投稿では簡単な電子取引での製品の一覧表示や検索サイトを構築しました。そこではMVCの背後にある高いレベルの概念をカバーしており、ASP.NET MVCプロジェクトを何も無いところから作成し、この電子取引製品の一覧表示機能を実装・テストする方法をご紹介しました。 このシリーズの2度目の投稿では、ASP.NET MVCフレームワークのURLルーティングアーキテクチャーについて深く掘り下げ、それがどのように動作するのか、またより上級なURLルーティングシナリオでどのようにそれを処理するのかについてお話しいたしました。

本日のブログ投稿ではコントローラがビューとどのようにやり取りするのか、特にクライアントへ戻した応答を描画するために、コントローラからビューへデータを引き渡す方法についてカバーしていきたいと思います。

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

このシリーズの最初の投稿では基本の製品の一覧表示や検索サポートを実装した電子取引サイトを作成しました。このサイトはASP.NET MVCフレームワークを使用して実装しているため、コントローラ、モデル、ビューのそれぞれのコンポーネントへコードが自然に構造化されました。

ブラウザがHTTPリクエストをWebサイトへ送信した時、ASP.NET MVC フレームワークはそのURL ルーティングエンジンを使用して、入って来たリクエストの処理を行うためにコントローラクラス上のアクションメソッドへマップします。MVCベースのアプリケーションにあるコントローラは入ってくるリクエストの処理、ユーザの入力と応答の処理、それらに基づくアプリケーションロジックの実行を行う役割を担っています。(データベースに保存されたモデルデータの取得および更新など)

クライアントに返されたHTMLのリスポンスを描画する時、コントローラは通常"ビュー"コンポーネントと動作します。これらはコントローラとは別のクラスやテンプレートとして実装され、表示ロジックをカプセル化することのみに焦点を当てることを目的としています。

ビューはどんなアプリケーションロジックまたはデータベースの取得コードも含むべきではなく、すべてのアプリケーションおよびデータロジックはコントローラクラスでのみ処理されるべきです。この分別の背後にある動機とは、アプリケーションおよびデータロジックをUI生成コードから完全に切り離すことにあります。これにより、UIの描画ロジックから隔離されたアプリケーションやデータロジックを簡単に単体テストできるようなります。

ビューは、コントローラクラスによってそこへ引き渡されたビュー特有のデータを使用した出力の描画だけを行うべきです。ASP.NET MVCフレームワークではこのビュー特有のデータを"ViewData"と呼んでいます。残りのこのブログ投稿ではこの"ViewData"を描画するためにコントローラからビューへ引き渡すいくつかのアプローチ方法をカバーしていきたいと思います。

簡単な製品の一覧表示シナリオ

コントローラからビューへViewDataを引き渡すためのテクニックをいくつかご覧頂くために、簡単な製品の一覧ページを構築していきます。:

CategoryID のinteger 値を使用してページに表示する製品にフィルタをかけます。URLの一部としてCategoryIDがどのように埋め込まれているかを上記でご覧いただけると思います。 (例えば: /Products/Category/2 または /Products/Category/4)

製品一覧のページはそこで2つの動的なコンテンツ要素を描画します。1つ目は表示するカテゴリの文字の名前です。 (例えば: "Condiments")2つ目は、製品名のHTML <ul><li/></ul>一覧です。上記のスクリーンショットでは両方に赤で丸く囲みました。

"ProductsController"クラスを実装するために使用する2つのアプローチ方法を以下で見てみます。クラスは、入ってくるリクエストを処理し、それを処理するために必要なデータを取得し、そしてこのデータを"List"ビューへ描画するために引き渡します。最初のアプローチでは遅延バインドのディクショナリオブジェクトを使用してデータを引き渡します。2つ目ののアプローチでは強く型付けされたクラスを使用してそれを引き渡します。

アプローチ 1: Controller.ViewDataディクショナリを使用してViewDataを引き渡す

コントローラベースクラスは"ViewData" ディクショナリプロパティを持っていて、それによりビューへ引き渡そうとするデータを紐付けることができます。ViewData ディクショナリへ値とキーのパターンでオブジェクトを追加します。

以下は"Category"アクションメソッドを持つProductsController クラスで、上記にある製品の一覧表示シナリオを実装します。上記で、どのようにカテゴリのID引数を使用してカテゴリの文字名を検索し、またそのカテゴリ内の製品の一覧を取得しているかをご確認ください。"CategoryName" および"Products"キーを使用してController.ViewDataコレクションにそれら両方を保存しています。 :

 

上記のカテゴリアクションはその時にRenderView("List") を呼び出してそれが描画しようとするビューテンプレートを表示します。このようにRenderViewを呼び出したとき、ViewDataディクショナリをビューへ引き渡して描画します。

ビューの実装

プロジェクトの\Views\Productsディレクトリの配下にあるList.aspxファイルを使用してListビューを実装します。このList.aspxページは¥Views\Sharedフォルダ(VS 2008で右クリックして、新しい項目の追加ーMVC ビューコンテントページを選択し、新しいビューページを作成した時にマスターページにつなげてください)の配下にあるSite.Masterマスターページのレイアウトを継承します。 :

MVC ビューコンテントページのテンプレートを使用してList.aspxページを作成した時に、それは通常のSystem.Web.UI.Pageクラスからではなく、どちらかというとSystem.Web.Mvc.ViewPageベースクラスから派生します。(これは既存のPageクラスのサブクラスです。):

ViewPageのベースクラスはViewDataディクショナリプロパティを提供し、それはビューページ内で使用してコントローラによって追加されたデータオブジェクトにアクセスすることができます。その後、これらのデータオブジェクトをサーバーコントロール、または<%= %>描画コードのどちらかを使用して、HTML出力を描画するためにそれらを使用することができます。

サーバコントロールを使用してビューを実装

以下は既存の<asp:literal>および<asp:repeater>サーバコントロールを使用してHTML UIを描画する方法の例になります。:

以下のコードビハインドクラスを使用してViewData をこれらのコントロールへバインドすることができます。(これを行うViewPageのViewDataディクショナリの使用方法に注目してください。):

注:ページ上に<form runat="server">が無いので、ViewStateが省略されません。上記のコントロールはまた自動的にどのIDの値も描画しません。つまり、省略されたHTMLを完全に制御できることを意味します。

<%= %> コードを使用してビューを実装

出力を生成するためにインラインの描画コードを使用したい場合は、以下のList.aspxを使用すれば上記と同じ結果を出すことができます。 :

注:ViewDateは"objects"を含んだディクショナリ型なため、foreachステートメントをその上で使用するには、ViewData["Products"] をList<Product>またはIEnumerable<Product>にキャストする必要があります。ページ上でSystem.Collections.GenericとMyStore.Modelsの名前空間の両方をインポートしてList<T>と製品の型を完全に認可しなくてもいいようにします。

注:上記での"var"キーワードの使用はVS 2008での新しいC#やVBの"型参照"機能の使用例です。(これについての以前の投稿はここをお読みください。)ViewData["Products"]をList<Product>としてキャストするため、List.aspxファイル内で製品の変数にすべてのインテリセンスが使用できます。:

アプローチ 2: 強く型付けされたクラスを使用してViewDataを引き渡す

遅延バインドディクショナリアプローチに加え、ASP.NET MVCフレームワークは、コントローラからビューへ強く型付けされたViewDataオブジェクトも引き渡すことができます。この強く型付けされたアプローチの使用にはいくつかの利点があります。:

  1. オブジェクトの検索に文字列の使用を回避し、コントローラとビューのコードの両方をコンパイル時にチェック
  2. C#のような強く型付けされた言語を使用する時ViewDataオブジェクトディクショナリから値を明示的にキャストする必要性を回避
  3. ビューページのマークアップとコードビハインドの両方でViewDataオブジェクトに対して自動のコードインテリセンスを取得
  4. アプリケーションや単体テストのコードベースに渡りコードリファクタリングツールを使用して変更の自動化をサポート

以下は強く型付けされた"ProductsListViewData"クラスで、製品の一覧表示を描画するためにList.aspxビューに必要なデータをカプセル化します。クラスにはCategoryNameおよび Productsプロパティがあります。 (新しいC#の自動プロパティ サポートを使用して実装):

ProductsController実装を更新して、このオブジェクトを使用し、強く型付けされたViewDataオブジェクトをビューへ引き渡すために使用することができます。:

RenderView() メソッドへ引数を追加することで、どのように強く型付けされたProductsListViewDataをビューへ引き渡しているかを上記でご確認ください。

強く型付けされたViewDataオブジェクトと一緒にビューのViewDataディクショナリを使用

以前書いたList.aspxビューの実装は更新したProductsController と引き続き動作します。コードの変更は必要ありません。これは強く型付けされたViewDataオブジェクトがViewPageから継承されたビューへ引き渡された時に、ViewDataディクショナリが値を検索するために強く型付けされたオブジェクトのプロパティに対して自動的にリフレクションを使用するためです。 :

RenderViewメソッドが呼ばれた時に引き渡した自動的に強く型付けされたProductsListViewDataオブジェクトCategoryNameプロパティから値を取得するために自動的にリフレクションを使います。

強く型付けされたViewDataにViewPage<T>ベースクラスを使用

ViewPageのベースクラスに基づいたディクショナリのサポートに加え、ASP.NET MVCフレームワークもジェネリックベースのViewPage<T>実装と一緒に出荷されます。ViewPage<T>(Tがコントローラがビューへ引き渡すViewDataクラスの型を示している)から派生したビューだった場合、ViewDataプロパティはこのクラスの型を使用して強く型付けされます。

例えば、ViewPageから派生していないList.aspx.csのコードビハインドクラスは更新できますが、ViewPage<ProductsListViewData>から継承されている場合はできません。:

これを行うとき、ページ上のViewDataプロパティはディクショナリからProductsListViewDataへ変更します。つまり、データを取得するために文字列ベースのディクショナリを使う代わりに現在では強く型付けされたプロパティを使用することができます。:

そしてサーバコントロールアプローチ、または<%= %>描画アプローチのどちらかを使用してViewDataに基づいてHTMLを描画することができます。

サーバコントロールを使用してViewPage<T>を実装

以下は<asp:literal>や<asp:repeater>サーバコントロールを使用してHTML UIを実装する例です。これはViewPageからList.aspxが派生した時に使用したマークアップと全く同じです。 :

以下は現在のコードビハインドの様子です。ViewPage<ProductsListViewData>から派生しているため直接プロパティへアクセスできていることにご注意ください。またキャストは必要ありません。(プロパティの1つを名前変更する際はいつでもリファクタリングツールサポートを受けることができます。) :

ViewPage<T><%= %>コードを使用してViewPage<T>を実装

もしインラインの描画コードを使用して出力を生成したい場合、以下のList.aspx を使用して上記と同じ結果を出すことができます。:

ViewPage<T>アプローチを使用すれば、ViewDataの文字検索を使用する必要はありません。それよりも重要なことは、どのプロパティも強く型付けされているため、キャストする必要がないということです。つまり、forearch(ViewData.Productsでのvar product)を書くことができ、Productsをキャストする必要がありません。またループ内で製品変数上ですべてのインテリセンスも取得することができます。:

まとめ

上記で、クライアントへのリスポンスを描画するためにコントローラがどのようにビューへデータを引き渡しているかについて、もう少し詳細にご説明出来ていれば幸いです。遅延ディクショナリまたは強く型付けされたどちらのアプローチでもこれを行うのに使用することができます。

最初にMVCアプリケーションを構築してみた時、もしかしたらUI生成コードからアプリケーションのコントローラロジックを分別して切り分けるという概念が少し変にお感じになるかもしれません。恐らく、リクエストの処理、それに対するアプリケーションロジック全ての実行、UIリスポンス構築に必要なViewDataのパッケージ化、そしてそれを別のビューページで描画させるために引き渡すという考え方に慣れるまで、一定のアプリケーション構築時間が必要になるだろうと思います。 重要: もしこのモデルに慣れない場合はお使いにならないでください。私たちは、MVCアプローチは全くのオプションのもので、万人の方が使用しなければいけないものだと思っておりません。

このアプリケーション分割の背後にある利点と目的は、UI描画コードから隔離したアプリケーションとデータロジックを起動およびテストできるようにすることです。これにより、アプリケーション構築するのに、アプリケーションに対する包括的な単体テストの開発、またTDD(テスト駆動型開発)ワークフローの使用がより簡単になります。今後のブログ投稿で、これについてより掘り下げていき、コードを簡単テストするために使用できる最善の方法をお話しいたします。

Hope this helps,

Scott

ScottGu's blog translated by Chica @ Wankuma 

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