ASP.NET MVC架构:ViewModel的组合,继承还是重复?

Cat*_*ICU 54 .net c# architecture asp.net-mvc

我正在使用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; }        
}
Run Code Online (Sandbox Code Playgroud)

在我编辑它时我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; }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下客户端验证 工作,但(编辑:客户端验证工作是巧合.)不起作用,服务器端验证失败,并显示以下消息:找不到名为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];
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,服务器端验证工作,客户端验证不再起作用.实现IClientValidatable似乎有点过于复杂,我不喜欢在这种情况下进行客户端验证.

2.通过继承

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

当尝试EditUserModel使用EF 直接保存它不起作用时,我得到一些关于EditUserModel元数据的错误消息,所以我使用AutoMapper来转换UserEditUserModel和向后转换.这个解决方案有效,但它更复杂,因为我必须从模型转换为视图模型并向后转换.

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; }        
}
Run Code Online (Sandbox Code Playgroud)

这是我最不喜欢的解决方案,因为代码重复(DRY)

问题

在这种情况下,继承,组合和重复的利弊是什么?

是否有一种简单的方法可以同时进行客户端和服务器端验证,而无需将模型转换为视图模型并向后转换?

Jos*_*son 26

在讨论过这个问题之前,我已经在各种情况下与这三个问题一起消失了.总的来说,我见过的大多数观点都支持MVC项目中的重复,并且ViewModel专门为每个视图构建.通过这种方式,您使用的约定就像UserDetailsViewModelUserCreateViewModel.正如您所说,在那时,AutoMapper或其他一些自动映射工具将用于从您的域对象转换为这些平面ViewModel.

虽然我也不喜欢重复代码,但我也不喜欢用验证或其他特定于视图的属性来污染我的域对象.另一个优点是,尽管几乎没有人会不得不与之抗争(无论所有专业人员都说过),但是你可以通过某种方式操纵你的域对象而不必操纵你的ViewModel.我之所以提到这一点,是因为它通常被引用,而不是因为它对我来说很重要.

最后,使用真正平坦的ViewModel可以实现更清晰的标记.当我使用合成时,我经常会创建错误,创建名称类似的HTML元素User.Address.Street.平面ViewModel至少减少了我这样做的可能性(我知道,我总是可以使用HtmlHelper例程来创建元素,但这并不总是可行的).

无论如何,我最近的项目也几乎需要单独的ViewModel.它们都是基于NHibernate的,并且在NHibernate对象上使用代理使得无法直接将它们用于视图.

更新 - 这是我过去提到的一篇好文章:http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx

  • 另外,不要将重复与DRY的违规混淆.仅仅因为你在两个实体上拥有相同名称的属性并不意味着你在重复自己.从语法上讲,是的,但从概念上讲,您的viewmodel和您的实体是完全不同的 - 特别是当您超出简单的CRUD应用程序时. (8认同)

Mal*_*sen 6

您也可以考虑域和视图模型的独立类,例如,在这种情况下

public class EditUserModel {    
  public string Name { get; set; }    
  public string Email { get; set; }    
  public string Password { get; set; }        
  public string ConfirmPassword { get; set; }        
}
Run Code Online (Sandbox Code Playgroud)

如果Id存储在url中.如果您想避免在User和EditorUserModel实例之间进行手动复制,AutoMapper可以为您提供帮助.这样,您可以轻松地将视图模型中的密码字符串与域模型中的密码哈希分离.