web-dev-qa-db-ja.com

ASP.NET MVCアーキテクチャ:構成、継承、または複製によるViewModel?

ASP.NET MVC 3とEntity Framework 4.1 Code Firstを使用しています。

Userエンティティがあるとしましょう:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }        
}

私のUserControllerで編集するとき、PasswordConfirmationフィールドを追加してPasswordConfirmation == Password

1.構成ごと

私の最初の試みは:

public class EditUserModel
{
    [Required]
    public User User { get; set; }

    [Compare("User.Password", ErrorMessage = "Passwords don't match.")]
    public string PasswordConfirmation { get; set; }
}

この場合、クライアント側の検証 動作しますが編集:クライアント側の検証の動作は偶然でした。)が機能しませんサーバーサイドの検証が失敗しました次のメッセージが表示されます:User.Passwordという名前のプロパティが見つかりませんでした

編集:この場合の最善の解決策は、カスタムCompareAttributeを作成することだと思います

IValidatableObjectの実装

public class EditUserModel : IValidatableObject
{
    [Required]
    public User User { get; set; }
    public string PasswordConfirmation { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if(this.PasswordConfirmation != this.User.Password)
            return new[] { new ValidationResult("Passwords don't match", new[] { "PasswordConfirmation " }) };

        return new ValidationResult[0];
    }
}

この場合、サーバー側の検証は機能しますがクライアント側の検証は機能しませんIClientValidatableの実装は少し複雑すぎるようで、この場合はクライアント側の検証を行わない方がいいと思います。

2.継承により

public class EditUserModel : User
{
    [Compare("Password", ErrorMessage = "Passwords don't match.")]
    public string PasswordConfirmation  { get; set; }
}

EFを使用してEditUserModelを直接保存しようとしても機能しません。EditUserModelメタデータに関するエラーメッセージが表示されるため、AutoMapperUserからEditUserModelに、またはその逆に変換します。この解決策は機能しますが、モデルからビューモデルに、またはその逆に変換する必要があるため、さらに複雑になります。

3.複製によって

(Malte Clasenの提案)

ビューモデルには、モデルのすべてのプロパティと追加のプロパティがあります。 AutoMapperは、あるものから別のものへの変換に使用できます。

public class EditUserModel {    
  public string Name { get; set; }    
  public string Email { get; set; }    
  public string Password { get; set; }   
  [Compare("Password", ErrorMessage = "Passwords don't match.")]     
  public string ConfirmPassword { get; set; }        
}

これは、コードの重複(DRY)のために私が最も嫌うソリューションです

質問

この場合の継承、構成、複製の長所と短所は何ですか?

モデルをビューモデルに変換したり、逆にしたりせずに、クライアント側とサーバー側の両方の検証を行う簡単な方法はありますか?

54
Catalin DICU

以前にこの質問に苦労したことがあるので、私はさまざまな場合に3つすべてに行きました。一般に、私が見た意見のほとんどは、MVCプロジェクトでの重複を支持しており、ViewModelは各ビュー用に特別に構築されています。この方法で使用する規則は、UserDetailsViewModelUserCreateViewModelのようなものです。あなたが言ったように、その時点でAutoMapperまたは他のいくつかの自動マッピングツールを使用して、ドメインオブジェクトをこれらのフラットなViewModelに変換します。

私も繰り返しコードが嫌いですが、検証や他のビュー固有の属性でドメインオブジェクトを汚染することも好きではありません。別の利点は、確かに誰もが対処する必要がないことは確かです(すべてのプロが言うことに関係なく)、必ずしもViewModelを操作しなくても、ドメインオブジェクトをいくつかの方法で操作できることです。私はそれが一般的に引用されているので、それが私にとって大きな重みを持っているのではなく、それを述べます。

最後に、真にフラットなViewModelを使用すると、マークアップが簡潔になります。コンポジションを使用すると、User.Address.Streetのような名前のHTML要素を作成するときにエラーが発生することがよくありました。フラットなViewModelは、少なくともそれを行う可能性を減らします(私は知っています、いつでもHtmlHelperルーチンを使用して要素を作成できますが、それが常に可能であるとは限りません)。

最近の私のプロジェクトでも、とにかく最近は個別のViewModelがかなり必要になっています。それらはすべてNHibernateベースであり、NHibernateオブジェクトでプロキシを使用すると、ビューに直接使用することができなくなります。

更新-これは私が過去に参照した良い記事です: http://geekswithblogs.net/michelotti/archive/2009 /10/25/asp.net-mvc-view-model-patterns.aspx

26
Josh Anderson

この例では、ドメインモデルとビューモデルの独立したクラスを検討することもできます。

public class EditUserModel {    
  public string Name { get; set; }    
  public string Email { get; set; }    
  public string Password { get; set; }        
  public string ConfirmPassword { get; set; }        
}

idがURLに格納されている場合。 UserとEditorUserModelのインスタンス間の手動コピーを避けたい場合は、 AutoMapper が役立ちます。このようにして、ドメインモデルのパスワードハッシュからビューモデルのパスワード文字列を簡単に分離できます。

6
Malte Clasen

私はこれを解決しようとしましたが、コードの複製を含まない解決策を見つけました。それは一種の回避策ですが、私の意見では、他の提案されたソリューションよりも優れています。

すべての検証を備えたユーザーモデルがあります。

public class UserModel
{
    [Required]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }        
}

前のモデルを新しいモデルで構成します

public class EditUserModel
{
    public UserModel User { get; set; }

    [Required]
    public string PasswordConfirmation { get; set; }
}

秘訣はアクションにあり、複数のモデルを受け取る可能性があります。

[HtttPost]
public ActionResult UpdateInformation(UserModel user, EditUserModel editUserModel) {
    if (ModelState.IsValid) {
         // copy the inner model to the outer model, workaround here:
         editUserModel.User = user
         // do whatever you want with editUserModel, it has all the needed information
    }
}

このようにして、検証は期待どおりに機能します。

お役に立てれば。

2
Mauro Ciancio

私はエンティティモデルをあまり使用していません。LINQ-SQLモデルを好むので、これは正しくない可能性があります。

エンティティに適用されるメタデータクラスを使用しないのはなぜですか? LINQ-SQLでは、割り当てられたメタデータが、クライアント側とサーバー側の両方の検証で考慮されます。

[MetaDataType]属性の適用は継承に似ていると私が理解していることから、基本エンティティへの変更のための新しいクラス(モデル)を実装しなくても機能するだけです。

また、試してみたい別のオプションとして、カスタム属性の作成があります。これは、同じような目的で1回だけ行いました。基本的に、メンバーの永続性を示すフラグ。

だから私は次のように定義されたエンティティを持っているでしょう:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }     

    [DoNotPersist]   
    public string ConfirmPassword {get; set;}

}

また、私はあなたがデータを保存するために何をしているのかわかりませんが、DataContextのOnInserting、OnEditing、OnDeleting関数にオーバーライドをフックして、基本的にカスタム属性を持つメンバーを削除しました。

データベースには保存されていないが、モデル関数やコントローラーなどのあらゆる場所で使用される各モデル(ビジネスインテリジェンス用の優れたUIを構築する)に一時的なアルゴリズムデータをたくさん使用するため、この方法は単純です。したがって、依存関係を使用します。すべてのモデルリポジトリとコントローラーでのインジェクション。したがって、テーブルごとにこれらの追加のデータポイントをすべて操作できます。

お役に立てば幸いです。

PS:-構成vs継承-それは実際にはアプリケーションのターゲットユーザーに依存します。セキュリティがそれほど問題ではなく、ユーザー/ブラウザー環境が制御されているイントラネットアプリの場合は、クライアント側の検証、つまり構成のみを使用します。

1
Varun Vohra

継承よりも構成を優先します。

ユーザーパスワードの場合、ユーザーテーブルにパスワードをクリアテキストで格納しているように見えます。

ソルトハッシュのみを保存する必要があり、EditUserModelには、パスワードとパスワード確認用の2つの文字列プロパティが必要です。これらは、テーブルのフィールドではありません。

0
Jakub Konecki