我正在使用 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 = =密码
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
元数据的错误消息,因此我使用 AutoMapper 从 User
转换为 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)
问题
在这种情况下,继承、组合和重复的优点和缺点是什么?
是否有一种简单的方法可以同时进行客户端和服务器端验证,而无需将模型转换为视图模型以及向后转换?
I'm using ASP.NET MVC 3 and Entity Framework 4.1 Code First.
Let's say I have a User
entity :
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
When editing it in my UserController
I want to add a PasswordConfirmation
field and verify that PasswordConfirmation == Password
1. By composition
My first try was :
public class EditUserModel
{
[Required]
public User User { get; set; }
[Compare("User.Password", ErrorMessage = "Passwords don't match.")]
public string PasswordConfirmation { get; set; }
}
In this case the client side validation works but (Edit: client side validation working was a coincidence.) doesn't work and the server side validation fails with the following message : Could not find a property named User.Password
Edit: I think the best solution, in this case, would be to create a custom CompareAttribute
Implementing 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];
}
}
In this case the server side validation works but the client side validation doesn't work anymore. Implementing IClientValidatable
seems a bit too complicated and I prefer not having client side validation in this case.
2. By inheritance
public class EditUserModel : User
{
[Compare("Password", ErrorMessage = "Passwords don't match.")]
public string PasswordConfirmation { get; set; }
}
When trying to directly save EditUserModel
using EF it doesn't work, I get some some error message about the EditUserModel
metadata so I'm using AutoMapper to convert from User
to EditUserModel
and backwards.
This solution works but it more complex because I have to convert from the model to the view model and backwards.
3. By duplication
(Suggested by Malte Clasen)
The view model would have all the properties of the model plus additional ones. AutoMapper can be used to convert from one to another.
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; }
}
This is the solution I like the least because of code duplication (DRY)
Questions
What are the pros and cons of inheritance, composition and duplication in this case ?
Is there a simple way to have both client side and server side validation without having to convert the model to the view model and backwards ?
发布评论
评论(5)
之前我曾纠结过这个问题,但在很多情况下我都同意这三个问题。一般来说,我看到的大多数意见都赞成在 MVC 项目中进行重复,并为每个视图专门构建一个 ViewModel。通过这种方式,您使用的约定类似于
UserDetailsViewModel
和UserCreateViewModel
。正如您所说,那时 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
Having struggled with this question before, I have in various instances gone with all three. In general, most of the opinions I've seen favor duplication in an MVC project, with a ViewModel constructed specifically for each view. In this manner the convention you'd use is something like
UserDetailsViewModel
andUserCreateViewModel
. As you said, at that point AutoMapper or some other auto mapping tool would be used to convert from your domain objects to these flat ViewModels.While I, too, don't like repeating code, I also don't like polluting my domain objects with validation or other view-specific attributes. Another advantage, though admittedly one almost nobody would ever have to contend with (regardless of what all the pros say), is that you can manipulate your domain objects in some ways without necessarily manipulating your ViewModels. I mention that because it's commonly cited, not because it carries much weight for me.
Lastly, using a truly flat ViewModel makes for cleaner markup. When I've used composition, I've often made errors creating HTML elements with names that are something like
User.Address.Street
. A flat ViewModel reduces at least my likelihood of doing that (I know, I could always use HtmlHelper routines to create elements, but that's not always feasible).My recent projects have also pretty much required separate ViewModels these days anyway. They've all been NHibernate-based, and the use of proxies on NHibernate objects makes it not possible to use them directly for views.
Update - here's a good article I've referred to in the past: http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx
您还可以考虑域和视图模型的独立类,在这种情况下,例如
,如果 Id 存储在 url 中。如果您想避免 User 和 EditorUserModel 实例之间的手动复制,AutoMapper 可以帮助您。通过这种方式,您可以轻松地将视图模型中的密码字符串与域模型中的密码哈希解耦。
You could also consider independent classes for domain and view models, in this case for example
if the Id is stored in the url. If you want to avoid the manual copy between the instances of User and EditorUserModel, AutoMapper can help you. This way you can easily decouple the password string in your view model from the password hash in your domain model.
我试图解决这个问题,并找到了一个不涉及重复代码的解决方案。这是一种解决方法,但在我看来,它比其他建议的解决方案更好。
您拥有包含所有验证的用户模型:
您将前一个模型与新模型组合在一起
。技巧在于操作,您可以收到多个模型:
通过这种方式,验证可以按预期工作。
希望这有帮助。
I have trying to work this out and I found a solution that does not involve duplicating code. It's kind of workaround but, in my opinion, it's better than the other proposed solutions.
You have the User Model with all the validation:
You compose the previous model with a new model
The trick is in the action, you could receive more than one model:
In this way the validation works as expected.
Hope this helps.
我不太使用实体模型,我更喜欢 LINQ - SQL 模型,因此这可能是不正确的:
为什么不使用应用于实体的元数据类?
使用 LINQ - SQL,客户端和服务器端验证都会考虑分配的元数据。
据我了解,[MetaDataType] 属性的应用与继承类似,只是它无需实现新类(模型)来更改基本实体即可工作。
另外,您可能想要尝试的另一个选项是创建自定义属性 - 我出于类似目的这样做过一次。本质上是一个标志,表明成员的坚持。
所以我会有一个定义如下的实体:
另外,我不知道你在做什么来存储数据,但我已经将重写挂接到我的 DataContext 的 OnInserting 、 OnEditing 、 OnDeleting 函数中,这基本上删除了具有我的自定义属性的任何成员。
我喜欢这种简单的方法,因为我们为每个模型使用大量临时的、而非算法的数据(为商业智能构建良好的 UI),这些数据不会保存在数据库中,而是在模型函数、控制器等内部的任何地方使用 - 所以我们使用依赖关系注入所有模型存储库和控制器中,因此我们可以为每个表提供所有这些额外的数据点。
希望有帮助!
PS:- 组合与继承 - 这实际上取决于应用程序的目标用户。如果对于安全性不太重要并且用户/浏览器环境受到控制的内部网应用程序,则只需使用客户端验证,即:组合。
I don't use Entity Models too much, I prefer LINQ - SQL models so this may be incorrect:
Why not use a meta-data class which is applied to the Entity?
With LINQ - SQL the metadata assigned is taken into consideration for both client-side as well as server-side validation.
From what I understand application of a [MetaDataType] attribute is similar to inheritance only it works without implementing a new class (model) for alterations to the basic entity.
Also, another option you might want to try is creating a custom attribute - I did this once for a similar purpose. Essentially a flag which indicated the persistence of a member.
So i would have an entity defined as follows:
Also, I don't know what you are doing to store data but I had hooked an override into the OnInserting , OnEditing, OnDeleting functions for my DataContext which basically removed any members having my custom attribute.
I like this method simple because we use a lot of temporary, rather algorithmic data for each model (building good UI's for Business Intelligence) which is not saved in the database but is used everywhere inside model functions, controllers, etc - so we use dependency injection in all model repositories and controllers and so we have all these extra data points for each table to play with.
Hope that helps!
PS:- Composition vs Inheritance - it really depends on the target user of the application. If it is for an intranet app where security is less of an issue and the user / browser environment is controlled then just use client side validation, ie: composition.
我更喜欢组合而不是继承。
对于您的用户密码,看起来您实际上是将密码以明文形式存储在“用户”表中,这是非常非常糟糕的。
您应该只存储加盐哈希,并且您的
EditUserModel
应该有两个用于密码和密码确认的字符串属性,这不是表中的字段。I would favour composition over inheritance.
In case of your user password it looks like you're actually storing the password in Users table in clear text, which is VERY, VERY BAD.
You should store only a salted hash, and your
EditUserModel
should have two string properties for password and password confirmation, which are NOT the fields in your table.