ASP.NET MVC体系结构:ViewModel是由构成,inheritance还是重复?
我使用ASP.NET MVC 3和entity framework4.1代码优先。
比方说,我有一个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
字段并validationPasswordConfirmation == Password
1.通过构图
我的第一个尝试是:
public class EditUserModel { [Required] public User User { get; set; } [Compare("User.Password", ErrorMessage = "Passwords don't match.")] public string PasswordConfirmation { get; set; } }
在这种情况下,客户端validation 工作但是 ( 编辑:客户端validation工作是巧合。) 不起作用 , 服务器端validation失败 ,出现以下消息: 无法find名为User.Password属性
编辑:我认为最好的解决scheme,在这种情况下,将创build一个自定义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]; } }
在这种情况下, 服务器端validation工作,但客户端validation不再工作 。 实现IClientValidatable
似乎有点太复杂,我更喜欢在这种情况下没有客户端validation。
2.通过inheritance
public class EditUserModel : User { [Compare("Password", ErrorMessage = "Passwords don't match.")] public string PasswordConfirmation { get; set; } }
当试图直接使用EF保存EditUserModel
它不起作用,我得到了一些有关EditUserModel
元数据的一些错误消息,所以我使用AutoMapper从User
转换到EditUserModel
和向后。 这个解决scheme的工作,但它更复杂,因为我必须从模型转换到视图模型和倒退。
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; } }
这是我最不喜欢的解决scheme,因为代码重复(DRY)
问题
在这种情况下inheritance,组成和重复有什么优点和缺点?
有没有简单的方法来同时进行客户端和服务器端validation,而不必将模型转换为视图模型和向后?
以前我一直在努力解决这个问题,在这三个问题上我都有过不同的情况。 一般来说,大多数的观点都赞成MVC项目中的重复,而ViewModel专门为每个视图构build。 以这种方式,您将使用的惯例是像UserDetailsViewModel
和UserCreateViewModel
。 正如你所说的那样,在这一点上AutoMapper或其他自动映射工具将被用来从你的域对象转换到这些平面ViewModels。
虽然我也不喜欢重复代码,但我也不喜欢用validation或其他视图特定的属性来污染我的域对象。 另外一个好处,虽然几乎没有人能够应付(不pipe所有的专业人士都说了什么),但是你可以通过某些方式来操作你的域对象,而不必操纵你的ViewModel。 我提到,因为它通常被引用,而不是因为它对我很重。
最后,使用一个真正的平面ViewModel使清洁标记。 当我使用构图时,我经常犯的错误创build的HTML元素的名称就像User.Address.Street
。 一个平面ViewModel至less减less了我做这件事的可能性(我知道,我总是可以使用HtmlHelper例程来创build元素,但这并不总是可行的)。
无论如何,我最近的项目也需要单独的ViewModel。 他们都是基于NHibernate的,NHibernate对象上的代理使用使得它不可能直接用于视图。
更新 – 这是我以前提到的一篇很好的文章: http : //geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx
例如,在这种情况下,您也可以考虑用于域和视图模型的独立类
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可以帮助你。 这样,您可以轻松地将视图模型中的密码string从域模型中的密码哈希中解耦。
我试图解决这个问题,并且find了一个不涉及重复代码的解决scheme。 这是一种解决方法,但在我看来,它比其他解决scheme更好。
您拥有所有validation的用户模型:
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 } }
这样validation按预期工作。
希望这可以帮助。
我不太使用实体模型,我更喜欢LINQ – SQL模型,所以这可能是不正确的:
为什么不使用应用于实体的元数据类? 使用LINQ-SQL,将分配的元数据同时考虑到客户端以及服务器端validation。
从我所了解的[MetaDataType]属性的应用类似于inheritance,只有它的工作没有实现一个新的类(模型)改变基本的实体。
另外,你可能想要尝试的另一个select是创build一个自定义属性 – 我曾经为了一个类似的目的做了一次。 本质上是一个标志,表明一个成员的坚持。
所以我会有一个实体定义如下:
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函数,基本上删除了任何具有我的自定义属性的成员。
我喜欢这个方法,因为我们为每个模型使用了很多临时的,相当algorithm的数据(构build商业智能的良好用户界面),这些数据没有保存在数据库中,而是在模型函数,控制器等内部的任何地方使用 – 所以我们使用依赖在所有模型库和控制器中进行注入,所以我们为每个表使用所有这些额外的数据点。
希望有所帮助!
PS: – 组合vsinheritance – 它确实取决于应用程序的目标用户。 如果是针对安全问题较less的Intranet应用程序,并且用户/浏览器环境受到控制,则只需使用客户端validation即组合。
我赞成组合而不是inheritance。
在用户密码的情况下,它看起来实际上是以明文forms存储在用户表中的密码,这是非常非常糟糕的。
您应该只存储一个盐EditUserModel
散列,而您的EditUserModel
应该有两个string属性,用于密码和密码确认,这不是您的表中的字段。