従来のアプローチまたは理論に従って、ViewModelはユーザーインターフェイスレイヤーの一部である必要があります。少なくともその名前はそう言っています。
しかし、Entity Framework、MVC、リポジトリなどを使用して自分で実装することに取り掛かると、別のことに気づきます。
誰かがエンティティ/DBモデルをViewModels(最後に記載されているDTO)にマップする必要があります。これは、[A] UIレイヤー(コントローラーによる)で実行する必要がありますか、それとも[B]サービスレイヤーで実行する必要がありますか?
オプションBを使用します。複数のエンティティモデルが組み合わされてViewModelを形成するという単純な事実のため、オプションAはノーノーです。不要なデータをUIレイヤーに渡さない場合がありますが、オプションBでは、サービスはデータを操作し、マッピング後に(ViewModelに)必要な/最小限のデータのみをUIレイヤーに渡すことができます。
それでも、オプションAを使用して、ViewModelをUIレイヤー(およびエンティティモデルをサービスレイヤー)に配置しましょう。
サービスレイヤーをViewModelにマップする必要がある場合、サービスレイヤーはUIレイヤーのViewModelにアクセスする必要があります。どのライブラリ/プロジェクト?ビューモデルはUIレイヤーの別のプロジェクトにある必要があり、このプロジェクトはサービスレイヤーによって参照される必要があります。ViewModelが別のプロジェクトにない場合は、循環参照があるので、行きません。サービスレイヤーがUIレイヤーにアクセスするのは厄介に見えますが、それでも対処できます。
しかし、このサービスを使用している別のUIアプリがある場合はどうなりますか?モバイルアプリがある場合はどうなりますか?ViewModelはどのように異なりますか?サービスは同じビューモデルプロジェクトにアクセスする必要がありますか?すべてのUIプロジェクトは同じViewModelプロジェクトにアクセスしますか、それとも独自のプロジェクトを持っていますか?
これらの考慮事項の後、私の答えは、Viewmodelプロジェクトをサービスレイヤーに配置することです。とにかく、すべてのUIレイヤーはサービスレイヤーにアクセスする必要があります。そして、それらすべてが使用できる類似のViewModelが多数存在する可能性があります(したがって、サービスレイヤーのマッピングが容易になります)。最近のマッピングはlinqを介して行われていますが、これはもう1つの利点です。
最後に、DTOについてのこの議論があります。また、ViewModelsのデータ注釈についても説明します。データ注釈付きのViewModel(Microsoft.Web.Mvc.DataAnnotations.dll)は、UIレイヤーに常駐する代わりに、サービスレイヤーに常駐できません(ただし、ComponentModel.DataAnnotations.dllはサービスレイヤーに常駐できます)。すべてのプロジェクトが1つのソリューション(.sln)に含まれている場合、どのレイヤーに配置してもかまいません。エンタープライズアプリケーションでは、各レイヤーに独自のソリューションがあります。
したがって、DTOは実際にはViewModelです。これは、ほとんどの場合、2つの間に1対1のマッピングがあるためです(たとえば、AutoMapperを使用)。この場合も、DTOにはUI(または複数のアプリケーション)に必要なロジックがあり、サービスレイヤーに存在します。また、UIレイヤーViewModel(Microsoft.Web.Mvc.DataAnnotations.dllを使用する場合)は、DTOからデータをコピーし、いくつかの「動作」/属性を追加するだけです。
[今、この議論は興味深い方向に進んでいます...:I]
また、データ注釈属性はUI専用であるとは思わないでください。System.ComponentModel.DataAnnotations.dllを使用して検証を制限すると、同じViewModelをフロントエンドとバックエンドの検証に使用することもできます(したがって、UI-residing-ViewModel-copy-of-DTOを削除します)。さらに、属性はエンティティモデルでも使用できます。例:.ttを使用すると、Entity Frameworkデータモデルを検証属性で自動生成して、バックエンドに送信する前にmax-lengthなどのDB検証を実行できます。これにより、検証のためにUIからバックエンドへのラウンドトリップが節約されます。また、セキュリティ上の理由から、バックエンドで再検証することもできます。したがって、モデルは自立型のバリデーターであり、簡単に渡すことができます。もう1つの利点は、バックエンドの検証がDBで変更された場合、.tt(DBの詳細を読み取り、エンティティクラスの属性を作成する)が自動的にそれを取得することです。これにより、UI検証ユニットテストも失敗する可能性があります。これは大きなプラスです(誤って忘れて失敗するのではなく、修正してすべてのUI /コンシューマーに通知できます)。はい、議論は優れたフレームワーク設計に向かって進んでいます。ご覧のとおり、階層ごとの検証、単体テスト戦略、キャッシング戦略など、すべてが関連しています。
質問に直接関係していませんが。この必見のチャンネル9のリンクで言及されている「ViewModelFaçade」も探索する価値があります。それはビデオの11分49秒で正確に始まります。上記の現在の質問が整理されたら、これが次のステップ/思考になるためです:「ViewModelsを整理する方法は?」
そして最後に、これらのモデルとロジックの問題の多くは、RESTで解決できます。すべてのクライアントは、データを照会し、必要なデータのみを取得するためのインテリジェンスを持つことができるためです。また、モデルをUIに保持し、サーバー/サービスレイヤーのモデル/ロジックはありません。その場合、唯一の重複は、各クライアントが実行する必要のある自動テストになります。また、データに変更があった場合、変更に適応しないと一部のクライアントが失敗します。問題は、サービスレイヤー(およびそれらが運ぶモデル)を完全に削除するのか、それともREST APIを呼び出すUIプロジェクト(モデルの問題がまだ続く)にサービスレイヤーをプッシュするのかということです。しかし、サービス層の責任に関しては、それらは関係なく同じです。
また、あなたの例では、「_ repository.ListContacts()」はリポジトリからViewModelを返しています。これは成熟した方法ではありません。リポジトリは、エンティティモデルまたはDBモデルを提供する必要があります。これはビューモデルに変換され、サービスレイヤーによって返されるのはこのビューモデルです。