読者です 読者をやめる 読者になる 読者になる

miso_soup3 Blog

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

EFMVC より、コントローラのPOSTアクションメソッドの流れを追う

コントローラのPOSTアクションメソッドで、とても綺麗だな〜と思ったコードがありましたので、それについて書きます。
GETなアクションメソッドはまた別の話です。

そのコードは EFMVC で書かれているものです。

綺麗だな〜と思ったコード

コードはこれです。(EFMVCのコードを少し変更したものです。)

/// コンストラクタ
public CategoryController(ICommandBus commandBus)
{
    this.commandBus = commandBus;
}

[HttpPost]
public ActionResult Save(CategoryFormModel form)
{
    if(!ModelState.IsValid)
        return View(form);
  
  //作成コマンド 
    var command = new CreateOrUpdateCategoryCommand(form.CategoryId,form.Name, form.Description);
  //検証
    IEnumerable<ValidationResult> errors = commandBus.Validate(command);
    ModelState.AddModelErrors(errors);

    if (!ModelState.IsValid)
        return View(form);
  //実行
    commandBus.Submit(command);

    return RedirectToAction("Index");   
}   

注目する点は、
コントローラーは、ICommandBusと、リクエストする内容、この2つだけを知っているだけでいい
ということです。

WEBアプリにおいてデータのコミットで重要なタスクとなるのは、

  • 検証
  • 実行
  • 次のページへ

この3つです。

これら3つの作業を、コントローラはICommandBusと、リクエストの2つだけで行っています。
コントローラが扱うものが少なくなると、テストの記述が簡単になりますし、
また、コードから何をしているのか確認しやすくなるという利点があります。

ICommandBusは全アクションメソッドで使えます。コピペ…コードの自動生成も楽ちんです。
そして、インターフェイスであり、名前の通り、コマンドのベルトコンベアみたいな役割を持っています。

つまり、コントローラは何をしているのかというと、
アクションメソッドに合うリクエストを作って、後はそのベルトコンベアにポイって投げているだけです。

ICommandBusがどのように実装されていて、ベルトコンベアがどこにいって、誰が実行しているのか、
モデルの設計がどのようになっているか、記載していきます。

コードの場所

コードは EFMVC からDownloadできます。

順番に追っていきます

先ほどのコードを例に追っていきます。

コマンドの作成
[HttpPost]
public ActionResult Save(CategoryFormModel form)
{
    if(!ModelState.IsValid)
        return View(form);

    var command = new CreateOrUpdateCategoryCommand(form.CategoryId,form.Name, form.Description);

実行するためのコマンドクラスを作成しています。
フォーム送信データ → 実行コマンド へのマッピングみたいなものなので、Automapperで行うのもありだと思います。

検証
IEnumerable<ValidationResult> errors = commandBus.Validate(command);

ICommandBusのValidateメソッドにコマンドを渡し、検証結果を受け取っています。
(このValidationResultは、EFMVC.Coreプロジェクトにある検証結果をもつクラスですが、
DataAnnotationのValidationResultでも良いと思います。)

ModelState.AddModelErrors(errors);

そしてその検証結果を、拡張メソッドを利用してModelStateに追加しています。
これも、モデルの検証結果 → コントローラのModelState というマッピングの1つだと思います。

検証の流れは以上です。

次は、実際に実装している部分を追います。

ICommandBusと検証の実装クラス

ICommandBusのValidateメソッドの呼び出し先は、
ICommandBusの実装クラス、EFMVC.CommandProcessor.Dispatcher.DefaultCommandBusクラスです。
DefaultCommandBusでは、指定された型に合う実装クラスを取得&実行します。
つまり、渡されたリクエストに合う、検証クラスを呼び出して実行します。

Validateメソッドが呼ばれた時に、> を実装するクラスを
MVCのDependencyResolverから取得します。取得されるクラスは、今の場合は
>の実装クラスである、EFMVC.Domain.Handlers.CanAddCategoryクラスです。

MVCのDependencyResolverから、実装クラスが取得できるようにするためには、
事前にIoCフレームワークのAutofacを使って、独自DependencyResolverをMVCに登録する必要があります。

場所は、EFMVC.WebプロジェクトのBootstrapperクラスです。

Interfaceと実装クラスのマッピングを設定した後の1番最後にある、
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
これです。(最近こればっかり書いているような・・・)

DefaultCommandBusクラスは、EFMVCではMVCのDependencyResolverを利用して実装していますが、
DependencyResolverを使わなくても実装できます。
UnityのIServiceLocatorを利用したり、自分でインスタンスを取得したり、自由に実装できます。要は具象クラスを呼び出せられればOKです。
私の場合はStructuremapを利用しているので

T handler = ObjectFactory.GetInstance<T>();

と書いています。

実行
commandBus.Submit(command);

実行も、検証の流れとほぼ同じです。
ICommandBusのSubmitメソッドにコマンドを渡しています。
(Submitメソッドは、boolを返していますが、意味がないのでvoidが良いと思います。)

検証は>でしたが、
実行の時は、>を呼び出しています。

実行後

実行後は、return View();でページ遷移です。

POSTアクションメソッドの流れは以上になります。

コントローラのPOSTアクションメソッドについて

以上、EFMVC より、POSTアクションメソッドの流れを追ってみました。

今一番試してみたい設計ですが、クラスの数が大量にならないかな〜という心配があります・・・。
でも、検証&実行は頻繁に使う流れなので、ICommandBusでまとめたいところです。
1つのリクエストを検証メソッドと実行メソッドの2つに通す、というのも綺麗です。

コントローラのPOSTのモデルは、そんなに複雑にならないので、そんなに凝らなくても大丈夫かな〜と思いますが…。
(検証ロジックと実行ロジックがまとまっていれば!)

問題は、GETです。
ViewModelの組み立てが大変で、現状問題ありありです。解決策はまだ見つかっておりません(+_+)

おまけ CQRSパターンについて

EFMVCは、CQRSパターンを利用しています。
”クエリ(取得)”と”コマンド(実行)”を、それぞれ独立した責務として明確にモデル化するというものです。
なので、取得はRepositoryに、コマンドはHandlerと別々になっています。

CQRSパターンについては
Windows Azure での CQRS
Greg Young流CQRS - Mark Nijhof
副作用を最小限に抑えるために必要なこと
に説明が載っています。(難しくてわからなかった…)