POSTアクションでビューモデルをドメインモデルにマップする方法は?


87

ViewModelsの使用とAutomapperの利用に関するインターネット上のすべての記事には、「コントローラー->ビュー」方向マッピングのガイドラインが記載されています。ドメインモデルとすべての選択リストを1つの特殊なViewModelに取り込み、それをビューに渡します。それは明らかで問題ありません。
ビューにはフォームがあり、最終的にはPOSTアクションになります。ここでは、すべてのモデルバインダーが、少なくともバインドと検証のための命名規則の一部で、元のViewModelに[明らかに]関連している[明らかに]別のビューモデルとともにシーンに登場します。

それをドメインモデルにどのようにマッピングしますか?

挿入アクションとすると、同じオートマッパーを使用できます。しかし、それが更新アクションだった場合はどうなりますか?リポジトリからドメインエンティティを取得し、ViewModelの値に従ってそのプロパティを更新して、リポジトリに保存する必要があります。

補遺1(2010年2月9日):モデルのプロパティを割り当てるだけでは不十分な場合があります。ビューモデルの値に従って、ドメインモデルに対して何らかのアクションを実行する必要があります。つまり、ドメインモデルでいくつかのメソッドを呼び出す必要があります。おそらく、ビューモデルを処理するために、コントローラーとドメインの間にある一種のアプリケーションサービスレイヤーが必要です...


次の目標を達成するために、このコードを整理する方法と配置する場所を教えてください。

  • コントローラを薄く保つ
  • SoCの実践を尊重する
  • ドメイン駆動設計の原則に従う
  • 乾く
  • つづく ...

回答:


37

私が使用しIBuilderのインタフェースを使用しておよびそれを実装ValueInjecterを

public interface IBuilder<TEntity, TViewModel>
{
      TEntity BuildEntity(TViewModel viewModel);
      TViewModel BuildViewModel(TEntity entity);
      TViewModel RebuildViewModel(TViewModel viewModel); 
}

...(実装)RebuildViewModelは単に呼び出すだけですBuildViewModel(BuilEntity(viewModel))

[HttpPost]
public ActionResult Update(ViewModel model)
{
   if(!ModelState.IsValid)
    {
       return View(builder.RebuildViewModel(model);
    }

   service.SaveOrUpdate(builder.BuildEntity(model));
   return RedirectToAction("Index");
}

ところで、私はViewModelを記述していません。入力を記述しています。これははるかに短いのですが、
それが役立つことを願っています。

更新: 現在、ProDinner ASP.net MVCデモアプリでこのアプローチを使用しています。これはIMapperと呼ばれ、このアプローチが詳細に説明されているPDFも提供されています。


私はこのアプローチが好きです。しかし、私がはっきりしていないことの1つは、特に階層型アプリケーションに照らして、IBuilderの実装です。たとえば、私のViewModelには3つのSelectListがあります。Builderの実装は、リポジトリから選択リストの値をどのように取得しますか?
Matt Murrell 2011

で@Mattマレルの外観prodinner.codeplex.com私はそこにこれを行う、と私はそれがIMapperが代わりにIBuilderの呼び出し
OMU

6
私はこのアプローチが好きです。ここにサンプルを実装しました:gist.github.com/2379583
Paul Stovell 2012

私の考えでは、ドメインモデルのアプローチに準拠していません。要件が不明確な場合のCRUDアプローチのようです。ドメインモデルでファクトリ(DDD)および関連するメソッドを使用して、合理的なアクションを伝えるべきではありませんか?このようにして、DBからエンティティをロードし、必要に応じて更新する方がよいでしょう。したがって、完全には正しくないようです。
Artyom 2015年

7

AutoMapperなどのツールを使用して、既存のオブジェクトをソースオブジェクトのデータで更新できます。更新のためのコントローラーアクションは次のようになります。

[HttpPost]
public ActionResult Update(MyViewModel viewModel)
{
    MyDataModel dataModel = this.DataRepository.GetMyData(viewModel.Id);
    Mapper<MyViewModel, MyDataModel>(viewModel, dataModel);
    this.Repostitory.SaveMyData(dataModel);
    return View(viewModel);
}

上記のスニペットに表示されているものとは別に:

  • モデルを表示するためのPOSTデータ+検証はModelBinderで行われます(カスタムバインディングで拡張できます)
  • エラー処理(つまり、リポジトリによるデータアクセス例外スローのキャッチ)は、[HandleError]フィルターによって実行できます。

コントローラのアクションは非常に薄く、関心の分離があります。マッピングの問題はAutoMapper構成で対処され、検証はModelBinderによって行われ、データアクセスはリポジトリによって行われます。


6
Automapperは平坦化を元に戻すことができないため、ここで役立つかどうかはわかりません。結局のところ、ドメインモデルはビューモデルのような単純なDTOではないため、いくつかのプロパティを割り当てるだけでは不十分な場合があります。おそらく、ビューモデルの内容に応じて、ドメインモデルに対していくつかのアクションを実行する必要があります。ただし、非常に優れたアプローチを共有するための+1。
Anthony Serdyukov 2010

@Anton ValueInjecterは平坦化を逆にすることができ;)
OMU

このアプローチでは、コントローラーを薄く保つことはなく、SoCとDRYに違反します... Omuが述べたように、マッピングを処理する分離されたレイヤーが必要です。
ルーキアン2010年

5

クライアントとの対話の両方向にViewModelという用語を再利用していると言いたいです。十分な数のASP.NETMVCコードを実際に読んだことがある場合は、ViewModelとEditModelの違いを見たことがあるでしょう。それは重要だと思います。

ViewModelは、ビューのレンダリングに必要なすべての情報を表します。これには、静的な非対話型の場所でレンダリングされるデータや、純粋にチェックを実行して何を正確にレンダリングするかを決定するためのデータが含まれる可能性があります。コントローラのGETアクションは、通常、ビューのViewModelをパッケージ化する役割を果たします。

EditModel(またはおそらくActionModel)は、ユーザーがそのPOSTに対して実行したいアクションを実行するために必要なデータを表します。したがって、EditModelは実際にアクションを記述しようとしています。これにより、ViewModelから一部のデータが除外される可能性があります。関連はありますが、実際には異なることを理解することが重要だと思います。

1つのアイデア

つまり、[モデル]-> [ViewModel]から移動するためのAutoMapper構成と、[EditModel]-> [モデル]から移動するための別の構成を非常に簡単に作成できます。次に、さまざまなコントローラーアクションでAutoMapperを使用する必要があります。EditModelには、モデルに対してそのプロパティを検証し、それらの値をモデル自体に適用するための関数が含まれている可能性があります。それは他に何もしていません、そしてあなたはとにかくリクエストをEditModelにマップするためにMVCにModelBindersを持っています。

別のアイデア

私が最近考えていることを超えて、ActionModelのアイデアからそのようなものが機能するのは、クライアントがあなたに投稿しているのは、実際にはユーザーが実行したいくつかのアクションの説明であり、1つの大きなデータの塊ではないということです。これには確かにクライアント側で管理するためのJavascriptが必要ですが、そのアイデアは興味深いと思います。

基本的に、ユーザーが提示した画面でアクションを実行すると、Javascriptはアクションオブジェクトのリストの作成を開始します。例としては、ユーザーが従業員情報画面を表示している可能性があります。従業員が最近結婚したため、姓を更新し、新しい住所を追加します。カバーの下で、これはリストへのChangeEmployeeNameAddEmployeeMailingAddressオブジェクトを生成します。ユーザーが[保存]をクリックして変更をコミットし、2つのオブジェクトのリストを送信します。各オブジェクトには、各アクションの実行に必要な情報のみが含まれています。

デフォルトのものよりもインテリジェントなModelBinderが必要ですが、優れたJSONシリアライザーは、クライアント側のアクションオブジェクトからサーバー側のアクションオブジェクトへのマッピングを処理できるはずです。サーバー側のもの(2層環境にいる場合)は、それらが動作するモデルでアクションを完了するメソッドを簡単に持つことができます。したがって、Controllerアクションは、モデルインスタンスがプルするIDと、それに対して実行するアクションのリストを取得するだけになります。または、アクションにIDを含めて、アクションを非常に分離します。

したがって、おそらくこのようなものがサーバー側で実現されます。

public interface IUserAction<TModel>
{
     long ModelId { get; set; }
     IEnumerable<string> Validate(TModel model);
     void Complete(TModel model);
}

[Transaction] //just assuming some sort of 2-tier with transactions handled by filter
public ActionResult Save(IEnumerable<IUserAction<Employee>> actions)
{
     var errors = new List<string>();
     foreach( var action in actions ) 
     {
         // relying on ORM's identity map to prevent multiple database hits
         var employee = _employeeRepository.Get(action.ModelId);
         errors.AddRange(action.Validate(employee));
     }

     // handle error cases possibly rendering view with them

     foreach( var action in editModel.UserActions )
     {
         var employee = _employeeRepository.Get(action.ModelId);
         action.Complete(employee);
         // against relying on ORMs ability to properly generate SQL and batch changes
         _employeeRepository.Update(employee);
     }

     // render the success view
}

ModelBinderを使用して正しいIUserActionインスタンスを取得し、IUserActionインスタンスを使用して正しいロジック自体を実行するか、(おそらく)情報を使用してモデルを呼び出すため、ポストバックアクションはかなり一般的なものになります。

3層環境にいる場合、IUserActionを単純なDTOにして、境界を越えてショットし、アプリ層で同様の方法で実行することができます。そのレイヤーの実行方法によっては、非常に簡単に分割でき、トランザクションに残る可能性があります(頭に浮かぶのは、Agathaの要求/応答であり、DIとNHibernateのIDマップを利用しています)。

とにかく、それは完璧なアイデアではないと確信しています。管理するにはクライアント側のJSが必要であり、プロジェクトがどのように展開するかを確認することはまだできていませんが、投稿はその方法を考えようとしていました。そこに行ってまた戻ってくるので、私は自分の考えを述べると思いました。それがお役に立てば幸いです。相互作用を管理する他の方法について聞いてみたいと思います。


面白い。ViewModelとEditModelの違いについて...編集関数の場合、ViewModelを使用してフォームを作成し、ユーザーが投稿したときにEditModelにバインドすると思いますか?もしそうなら、検証エラーのためにフォームを再投稿する必要がある状況にどのように対処しますか(たとえば、ViewModelにドロップダウンを設定する要素が含まれている場合)-EditModelにもドロップダウン要素を含めるだけですか?その場合、2つの違いは何でしょうか?
upTheCreek 2010年

EditModelを使用していてエラーが発生した場合、ViewModelを再構築する必要があり、非常にコストがかかる可能性があるという懸念があると思います。ViewModelを再構築し、ユーザー通知メッセージ(おそらく検証エラーなどの肯定的なメッセージと否定的なメッセージの両方)を配置する場所があることを確認します。パフォーマンスの問題であることが判明した場合は、そのセッションの次のリクエストが終了するまで(おそらくEditModelの投稿である)、いつでもViewModelをキャッシュできます。
ショーンコペンヘイバー2010年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.