私はViewModelを使用するのはかなり新しいのですが、ドメインモデルのインスタンスをプロパティとしてViewModelに含めることは許容されますか、またはそれらのドメインモデルのプロパティはViewModel自体のプロパティである必要がありますか?たとえば、クラスAlbum.cs
がある場合
public class Album
{
public int AlbumId { get; set; }
public string Title { get; set; }
public string Price { get; set; }
public virtual Genre Genre { get; set; }
public virtual Artist Artist { get; set; }
}
通常、ViewModelにAlbum.cs
クラスのインスタンスを保持させるか、ViewModelにAlbum.cs
クラスの各プロパティのプロパティを持たせますか。
public class AlbumViewModel
{
public Album Album { get; set; }
public IEnumerable<SelectListItem> Genres { get; set; }
public IEnumerable<SelectListItem> Artists { get; set; }
public int Rating { get; set; }
// other properties specific to the View
}
public class AlbumViewModel
{
public int AlbumId { get; set; }
public string Title { get; set; }
public string Price { get; set; }
public IEnumerable<SelectListItem> Genres { get; set; }
public IEnumerable<SelectListItem> Artists { get; set; }
public int Rating { get; set; }
// other properties specific to the View
}
ViewModelがドメインモデルのインスタンスを含むことは許容されますか?
基本的にそうではありません。文字通り2つのレイヤーを混合し、それらを結合しているからです。私は認めなければなりません、私はそれがたくさん起こると思います、そしてそれはあなたのプロジェクトの quick-win-level に少し依存しますが、私たちはそれが[〜#〜] solid [〜#〜] の単一責任の原則.
楽しい部分:これはMVCのビューモデルに限定されず、実際には 古き良きデータ、ビジネス、UIレイヤー の分離の問題です。これについては後で説明しますが、今のところは。これはMVCに適用されますが、さらに多くのデザインパターンにも適用されます。
まず、いくつかの一般的な適用可能な概念を指摘し、後で実際のシナリオと例を拡大します。
レイヤーを混ぜないことの長所と短所を考えてみましょう。
常に問題があります。まとめて、後で説明し、通常は適用されない理由を示します。
常に勝利があります。まとめて、後で説明し、これが実際に理にかなっている理由を示します
そうではありません [〜#〜] dry [〜#〜] !
おそらく他のクラスとまったく同じである追加のクラスが必要になります。
これは無効な引数です。異なるレイヤーには明確に定義された異なる目的があります。したがって、1つのレイヤーに存在するプロパティは、他のプロパティとは異なる目的を持っています-プロパティが同じ名前であっても!
例えば:
これはあなた自身を繰り返すことではありません:
_public class FooViewModel
{
public string Name {get;set;}
}
public class DomainModel
{
public string Name {get;set;}
}
_
一方、マッピングを2回定義すると、 is が繰り返されます。
_public void Method1(FooViewModel input)
{
//duplicate code: same mapping twice, see Method2
var domainModel = new DomainModel { Name = input.Name };
//logic
}
public void Method2(FooViewModel input)
{
//duplicate code: same mapping twice, see Method1
var domainModel = new DomainModel { Name = input.Name };
//logic
}
_
もっと面倒です!
ほんとに?コーディングを開始すると、モデルの99%以上が重複します。一杯のコーヒーをつかむには時間がかかります;-)
「より多くのメンテナンスが必要です」
はい、あります。そのため、マッピングを単体テストする必要があります(そして、マッピングを繰り返さないでください)。
いいえ、違います。レイヤーが追加され、より複雑になります。複雑さを増すことはありません。
私の賢い友人は、かつてこのように述べました:
「飛行平面は非常に複雑なものです。落下平面は非常に複雑です。」
このような定義を使用しているのは彼だけではありません 、違いは予測可能性にあり、 entropy と実際の関係があります chaos 。
一般的に:パターンは複雑さを追加しません。これらは、複雑さを軽減するために存在します。彼らはよく知られている問題の解決策です。明らかに、実装が不十分なパターンは役に立たないため、パターンを適用する前に問題を理解する必要があります。問題を無視しても効果はありません。いつか返済しなければならない技術的負債を追加するだけです。
レイヤーを追加すると、明確に定義された動作が得られます。これは明らかな追加のマッピングにより、(ビット)より複雑になります。さまざまな目的でレイヤーを混合すると、変更が適用されたときに予期しない副作用が発生します。データベース列の名前を変更すると、UIのキー/値ルックアップに不一致が生じ、既存のAPI呼び出しを実行できなくなります。これについて考えてみましょう。これがデバッグ作業と保守コストにどのように関係するかを考えてください。
はい、マッピングを追加すると、CPUパワーが余分に消費されます。ただし、これは(Raspberry Piがリモートデータベースに接続されている場合を除いて)、データベースからデータをフェッチする場合に比べると無視できます。結論:これが問題の場合:キャッシュを使用します。
これは何を意味するのでしょうか?
これ以上の組み合わせ:
つまり、厄介な副作用を心配することなく、明確に定義されたコードを変更することで、変更を加えることができます。
"これは変更を反映するためのものであり、変更されることはありません!"
変化が来るでしょう: 毎年何兆ドルもの米ドルを使う は単純に通り過ぎることはできません。
まあそれはいいです。しかし、開発者としてそれに直面してください。間違いのない日が仕事をやめる日です。同じことがビジネス要件にも当てはまります。
「私の(マイクロ)サービスまたはツールは、それを処理するのに十分小さいです!」
ここには実際に良い点があるので、これは最も難しいかもしれません。一度使用するものを開発する場合、おそらく変更にまったく対応できず、とにかくそれを再構築する必要があります。提供実際にそれを再利用します。それにもかかわらず、他のすべてのもの:「変更が来る」だから、なぜ変更をより複雑にするのですか?また、おそらく、最小限のツールまたはサービスでレイヤーを除外すると、通常はデータレイヤーが(ユーザー)インターフェースに近くなります。 APIを扱っている場合、実装にはバージョンの更新が必要であり、すべてのクライアントに配布する必要があります。一回のコーヒーブレイクの間にそれをすることができますか?
"今のところ、それをすばやく簡単に実行できます..."
あなたの仕事は "当分の間" ですか?冗談です;-)しかし;いつ修正しますか?おそらくあなたの技術的な負債があなたを強制するとき。当時、この短いコーヒーブレークよりも費用がかかりました。
「「変更のために閉じ、拡張のために開く」についてはどうですか?それもSOLID原則です!]
はい、そうです!しかし、これはタイプミスを修正すべきではないという意味ではありません。または、適用されたすべてのビジネスルールを拡張機能の合計として表現できること、または破損しているものを修正することが許可されていないこと。または Wikipedia のように:
モジュールが他のモジュールで使用できる場合、そのモジュールは閉じられていると言います。これは、モジュールに明確に定義された安定した説明(情報を隠すという意味でのインターフェース)が与えられていることを前提としています。
これは実際にレイヤーの分離を促進します。
さて、いくつかの典型的なシナリオ:
なぜなら、これが実際の質問で使用しているものだからです。
例を挙げましょう。次のビューモデルとドメインモデルを想像してください。
note:これは、他のタイプのレイヤーにも適用できます。たとえば、DTO、DAO、Entity、ViewModel、Domainなどです。
_public class FooViewModel
{
public string Name {get; set;}
//hey, a domain model class!
public DomainClass Genre {get;set;}
}
public class DomainClass
{
public int Id {get; set;}
public string Name {get;set;}
}
_
したがって、コントローラのどこかに FooViewModel を入力し、それをビューに渡します。
ここで、以下のシナリオを検討してください。
この場合は、おそらくビューも調整する必要があります。これは、関心事の分離というコンテキストでは悪い習慣です。
ViewModelをDomainModelから分離している場合は、マッピングの微調整(ViewModel => DomainModel(およびその逆))で十分です。
私はこれが実際のライブシナリオでうまくいかないのを見ました。
この場合の一般的な問題は、_@Html.EditorFor
_を使用すると、ネストされたオブジェクトの入力が発生することです。これには、Id
sおよびその他の機密情報が含まれる場合があります。これは、実装の詳細が漏洩することを意味します。実際のページはドメインモデル(おそらくデータベースのどこかに関連付けられている)に関連付けられています。このコースを進むと、hidden
入力を作成することがわかります。これをサーバー側のモデルバインディングまたはオートマッパーと組み合わせると、非表示のId
の操作をfirebugなどのツールでブロックしたり、プロパティに属性を設定し忘れたりすると、ビューで使用できるようになります。
これらのフィールドの一部をブロックすることは可能ですが、おそらく簡単ですが、ネストされたDomain/Dataオブジェクトが多ければ多いほど、この部分を正しくするのが難しくなります。そして;このドメインモデルを複数のビューで「使用」している場合はどうなりますか?それらは同じように動作しますか?また、必ずしもビューをターゲットにしているわけではない理由で、DomainModelを変更する場合があることにも注意してください。したがって、DomainModelを変更するたびに、それが might によってビューとコントローラーのセキュリティの側面に影響を与えることに注意する必要があります。
ドメインにビューに関するメタデータを本当に含めたいですか?または、データ層にビューロジックを適用しますか?ビューの検証は常にドメインの検証と同じですか?同じフィールドがありますか(またはそれらの一部は連結されていますか)?同じ検証ロジックがありますか?ドメインモデルクロスアプリケーションを使用していますか?等.
これが進むべき道ではないことは明らかだと思います。
私はあなたにもっと多くのシナリオを与えることができますが、それはより魅力的なものへの好みの問題です。私はこの時点であなたがポイントを獲得することを願っています:)それにもかかわらず、私はイラストを約束しました:
さて、本当に汚くてすぐ勝つためにはそれはうまくいきますが、私はあなたがそれを欲するべきではないと思います。
通常、ドメインモデルと80%以上類似しているビューモデルを構築するのは少しだけ手間がかかります。これは不必要なマッピングを行うように感じるかもしれませんが、最初の概念的な違いが発生すると、努力する価値があったことがわかります:)
代替として、私は一般的なケースのために次の設定を提案します:
automapper
のようなライブラリを使用して、一方から他方へのマッピングを作成します(これは_Foo.FooProp
_を_OtherFoo.FooProp
_にマッピングするのに役立ちます)利点は、例えば;データベーステーブルの1つに追加のフィールドを作成しても、ビューには影響しません。ビジネスレイヤーやマッピングに影響を与える可能性がありますが、そこで停止します。もちろん、ほとんどの場合、ビューも変更したいのですが、この場合、する必要はありません。したがって、コードの一部で問題を分離します。
これがWeb-API/ORM(EF)シナリオでどのように機能するかについてのもう1つの具体的な例:
ここではより直感的です。特にコンシューマがサードパーティの場合、ドメインモデルがコンシューマの実装と一致する可能性は低いため、ビューモデルは完全に自己完結型である可能性が高くなります。
note:「ドメインモデル」という名前は、DTOまたは「モデル」とも呼ばれます
Web(またはHTTPまたはREST)APIでは注意してください。通信は多くの場合、HTTPエンドポイントで公開されている実際の「もの」であるデータ転送オブジェクト(DTO)によって行われます。
したがって、これらのDTOをどこに配置すればよいでしょうか。それらはドメインモデルとビューモデルの間にありますか?はい、そうです;消費者がカスタマイズされたビューを実装する可能性が高いので、それらをviewmodel
として扱うのは難しいだろうことはすでに見てきました。
DTOはdomainmodels
を置き換えることができますか、それとも独自に存在する理由がありますか?一般に、分離の概念は_DTO's
_およびdomainmodels
にも適用できます。しかし、もう一度言いましょう。あなたは自分自身に尋ねることができます(そして、これが私が少し実用的である傾向があるところです)ドメイン内にdomainlayer
を明示的に定義するのに十分なロジックがありますか?サービスがどんどん小さくなると、logic
の一部である実際のdomainmodels
も減少し、一緒に除外されて、で終わる:
EF/(ORM) Entities
↔DTO
↔Consumers
免責事項/注記
@mrjoltcolaが述べたように:覚えておくべきコンポーネントのオーバーエンジニアリングもあります。上記のいずれにも該当せず、ユーザー/プログラマーが信頼できる場合は、問題ありません。ただし、DomainModelとViewModelの混合により、保守性と再利用性が低下することに注意してください。
技術的なベストプラクティスと個人の好みの組み合わせから、意見はさまざまです。
ビューモデルでドメインオブジェクトを使用したり、モデルとしてドメインオブジェクトを使用したりしても、何もありません間違っています。多くの人が行っています。すべてのビューに対してビューモデルを作成することを強く望んでいる人もいますが、個人的には、多くのアプリは、快適な1つのアプローチを学び、繰り返す開発者によって過剰に設計されていると感じています。真実は、ASP.NET MVCの新しいバージョンを使用して目標を達成する方法がいくつかあることです。
ビューモデルとビジネスおよび永続化レイヤーに共通のドメインクラスを使用する場合の最大のリスクは、モデルインジェクションのリスクです。モデルクラスに新しいプロパティを追加すると、それらのプロパティがサーバーの境界外に公開される可能性があります。攻撃者は、表示してはいけないプロパティ(シリアル化)を確認し、変更してはならない値(モデルバインダー)を変更する可能性があります。
インジェクションを防ぐには、全体的なアプローチに関連する安全な方法を使用してください。ドメインオブジェクトを使用する場合は、コントローラーまたはモデルバインダーのアノテーションでホワイトリストまたはブラックリスト(包含/除外)を使用してください。ブラックリストの方が便利ですが、将来のリビジョンを作成する怠惰な開発者は、それらを忘れたり、気づかない可能性があります。ホワイトリスト([Bind(Include = ...)]は必須であり、新しいフィールドが追加されると注意が必要になるため、インラインビューモデルとして機能します。
例:
[Bind(Exclude="CompanyId,TenantId")]
public class CustomerModel
{
public int Id { get; set; }
public int CompanyId { get; set; } // user cannot inject
public int TenantId { get; set; } // ..
public string Name { get; set; }
public string Phone { get; set; }
// ...
}
または
public ActionResult Edit([Bind(Include = "Id,Name,Phone")] CustomerModel customer)
{
// ...
}
最初のサンプルは、アプリケーション全体にマルチテナントの安全性を適用する良い方法です。 2番目のサンプルでは、各アクションをカスタマイズできます。
アプローチに一貫性を持たせ、プロジェクトで使用されているアプローチを他の開発者に明確に文書化します。
セキュリティ/演習として、ログイン/プロファイル関連機能のビューモデルを常に使用して、Webコントローラーとデータアクセスレイヤーの間のフィールドを「マーシャリング」することをお勧めします。