miso_soup3 Blog

主に ASP.NET 関連について書いています。

ASP.NET MVC コア拡張部

One ASP.NET Advent Calendar 2012 の 9 日の記事です。
前日の 8 日は ASP.NET MVC と WF4 を一緒に使う際に気をつけるたった一つのこと
後日は 10 日は WebFormっぽいコントロールベスト3こちらになります。

ASP.NET MVC は拡張性に優れており、あらゆるところで拡張が可能になっています。
その中で、一番フレームワークのコアに近いところである、コア拡張部について、
拡張することで何ができるか?を中心に紹介したいと思います。

めったに利用しない

これから紹介する拡張は、1アプリケーションに対して1回あるかないか…、
もしかしたら人生において、1回も体験することはないかもしれません。

が、万が一アプリケーション特有のなにか、が必要になった時、
これらの拡張が役立つことは十分にありえるかと思います。

目次

紹介する部分は以下の通りです。

  • 1.コントローラファクトリー ControllerFactory
  • 2.アクションインボーカー ActionInvoker
  • 3.モデルバインダー ModelBinder
  • 4.バリュープロバイダー ValueProvider
  • 5.モデルバインダープロバイダー ModelBinderProvider
  • 6.モデルメタデータプロバイダー ModelMetaDataProvider
  • 7.モデルバリデータプロバイダー ModelValidatorProvider
  • 8.ビューエンジン ViewEngine
  • 9.テンプデータプロバイダー TempDataProvider

1.コントローラファクトリー ControllerFactory

なにをするところか?

コントローラのインスタンスを生成しているところです。

デフォルト(置き換える前の実装を担当しているクラス)
  • DefaultControllerFactory

Controllers の名前配下に、〜〜Controller クラスを置くという決まりは、
このデフォルトの実装からきています。

拡張したら何ができるか?例えば:
  • コントローラを取得できない場合、独自の処理実行することができます。
  • コントローラを独自の namespace に置くことができます。
  • コントローラクラスのコンストラクタの引数に、任意の値をいれることができます。
設定できる範囲
  • アプリケーション全体
ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());

2.アクションインボーカー ActionInvoker

なにをするところか?
  • アクションメソッドを選択し、実行します。
  • アクションメソッドの前後で、アクションフィルターを実行し、最後には、ビューを呼び出します。
デフォルト
  • ControllerActionInvoker
拡張したら何ができるか?例えば:
  • アクションフィルターを WebConfig から動的に追加できます。
  • ある条件の時にだけ、アクションフィルターを無効にできます。
  • アクションフィルターとの結合テストのために、このアクションインボーカーを実装することもあります。
設定できる範囲
  • コントローラクラス毎

アクションインボーカーは、コントローラクラスに付属しているので、
コントローラ毎に違うアクションインボーカーを設定できます。

public HomeController()
{
	this.ActionInvoker = new MyActionInvoker();
}

3.モデルバインダー ModelBinder

なにをするところか?
  • リクエストデータ等から、コントローラに渡すモデル(アクションメソッドの引数に定義しているモデル)を作成します。
  • モデルを作成する時に、検証を行います。
デフォルト

HttpPostedFileBase や、byte[] のモデルバインダー等、複数あります。

拡張したら何ができるか?例えば:
  • 独自のクラスを、アクションメソッドの引数のモデルに定義することができます。
  • 年、月、日といった複数の日付のテキストボックスの値から、DateTime を作成して、アクションメソッドの引数に渡すことができます。参考サイト:Splitting DateTime - Unit Testing ASP.NET MVC Custom Model Binders
  • 入力値の補正。ひらがなで入力された値を、カタカナに補正して、アクションメソッドの引数に渡すことができます。(全角→半角など!)
設定できる範囲
  • アプリケーション全体
ModelBinders.Binders.DefaultBinder = new MyModelBinder();
  • コントローラのアクションメソッド単位
public ActionResult Index([ModelBinder(typeof(MyModelBinder))] MyModel model)
{
	return View();
}

この型は、このモデルバインダーを使って作成する、といった設定ができます。

ModelBinders.Binders.Add(typeof(MyModel), new MyModelBinder());
[ModelBinder(typeof(MyModelBinder))]
public class MyModel
{
}
ひとこと

モデルバインダーの拡張はよく利用されているかもしれません。
モデルの作成と検証をカプセル化できるので、本当のモデルを定義してどんどん使いたいところです。

相棒

ASP.NET MVC のモデルバインダーの仕組みでは、このモデルバインダー以外にも登場人物がいます。

  • モデルバインダープロバイダー
    • どのモデル(型)の時に、どのモデルバインダーを使用するかを決めます。
  • バリュープロバイダー
    • モデルを作成するためには、材料(Cookieの値や、送信された値)が必要で、その材料を提供してくれます。

次はこの2つについてです。

4.バリュープロバイダー ValueProvider

なにをするところか?
  • キー(文字列)から、リクエストの値を提供します。

先ほどのモデルバインダーへ値を提供するのが、このバリュープロバイダーです。

デフォルト

ルートデータ、フォーム送信値、等を提供するプロバイダーがあります。

拡張したら何ができるか?例えば:
  • アクションメソッドの引数のモデルを、Cookie や TempData、Session 等の値を使って作成されるようにすることができます。
  • ユーザ名を保存するかどうかのチェックを、Cookie から取得して、アクションメソッドの引数に渡されるようにできます。参考サイト:ASP.NET MVC Working Smart With Cookies ? Custom ValueProvider
  • アクションメソッドの引数に、今ログインしているユーザ名が入るようにすることができます。参考サイト:How to create a custom Value Provider for MVC
設定できる範囲
  • アプリケーション全体
ValueProviderFactories.Factories.Add(new MyValueProviderFactory());
補足

Cookie と TempData、Session などのバリュープロバイダーは既に ASP.NET MVC 3 Futures の中にあります。

5.モデルバインダープロバイダー

なにをするところか?

ある型を作成するためのモデルバインダーを動的に選択して提供します。

デフォルト

ありません。
モデルバインダーを選択する方法は、このプロバイダーを使う以外の方法があるので、
なくても問題ありません。

拡張したら何ができるか?例えば:

例えば、この型はこのモデルバインダー、あの型はこのモデルバインダー、と適用するには、
モデルバインダープロバインダーを使わずとも、3.モデルバインダーで記載したように
以下のように書くことで実現できます。

ModelBinders.Binders.Add(typeof(MyModel), new MyModelBinder());

が、上の場合、MyModel を継承した MyChildModel の場合は、MyModelBinder が適用されません。
ここで、モデルバインダープロバイダーの拡張の出番です。
↓のサイトにこの場合の実装例があります。
参考サイト:an inheritance-aware modelbinderprovider in mvc 3

適用範囲
  • アプリケーション全体
ModelBinderProviders.BinderProviders.Add(new MyModelBinderProvider);

6.モデルメタデータプロバイダー ModelMetaDataProvider

何をするところか?

モデルメタデータとは、モデルの付随情報のことで、表示名、テンプレート名、必須かどうか、などの情報が入っています。
これらのモデルメタデータは、どんな時に利用されるかというと、
ビューの表示の時や、モデルバインダ時(検証時も含む)等、フレームワークの様々な所で利用されます。
(@Html.LaborFor は、表示名を参照、検証時は必須かどうかの情報を参照、といった風に。)

デフォルト
  • DataAnnotationsModelMetadataProvider

属性をもとに、モデルメタデータを作成します。

拡張したら何ができるか?例えば:
適用範囲
  • アプリケーション全体
ModelMetadataProviders.Current = new MyModelMetadataProvider();	

7.モデルバリデータプロバイダー ModelValidatorProvider

何をするところか?

モデルを検証するモデルバリデータを提供します。

デフォルト

検証属性を使ったバリデータや、IDataErrorInfo を使ったバリデータ、数値のバリデータ(クライアント)
を提供します。

拡張したら何ができるか?例えば:
適用範囲
  • アプリケーション全体
ModelValidatorProviders.Providers.Add(new MyModelValidatorProvider());

8.ビューエンジン ViewEngine

何をするところか?
  • ビューオブジェクトを検索し、作成します。

ビューは "Index" といった様に文字列で指定されるので、それに一致する
ビューオブジェクト(HTML レスポンスを書きだすもの)を作成します。

デフォルト
  • WebFormViewEngine
  • RazorViewEngine

ビューは、Views フォルダ配下・Shared フォルダ配下に置く、といった決まりは、
このデフォルトの実装によるものです。

拡張したら何ができるか?例えば:
  • ビューである 〜.cshtml ファイルを、任意のフォルダ配下に置くことができます。
  • ユーザのカルチャに合わせて、〜.ja.cshtml・〜.is.csHtml などのビューを返すことができます。
注意

ASP.NET MVC 4 には、ユーザ・エージェントに合わせてビューを切り替える
Display Modes という機能があるので、ビューエンジンを実装するよりも、
Display Modes を選択した方が良いです。

9.テンプデータプロバイダー TempDataProvider

何をするところか?
  • 一時的な値を保持します。
デフォルト
  • SessionStateTempDataProvider

セッション状態を利用して値を保持します。

拡張したら何ができるか?例えば:
  • セッション状態が使えない場合に、別の方法で値を保持する、という手段をとることができます。
    • セッション状態ではなく、Cookie を利用して値を保持することができます。
適用範囲
  • コントローラ毎
public HomeController()
{
	this.TempDataProvider = new MyTempDataProvider();
}
補足

Cookie を利用したテンプデータプロバイダーは、既に ASP.NET MVC 3 Futures の中にあります。
(CookieTempDataProvider ) 

おしまい

ASP.NET MVC 拡張の全貌については、こちらの記事 ASP.NET MVCパイプラインと拡張性
が参考になります。
拡張の入門記事としては、こちらの記事 An Introduction to ASP.NET MVC Extensibility
が紹介されています。この記事の右側にある、ASP.NET MVC Pipeline.pdf というファイルは
ASP.NET MVC フレームワークの理解にとても役立ちます。