One common recommended practice in asp.net mvc is that you should not send your business models to your views.. instead you should create viewmodels specific to each view.
asp.net mvc中一个常见的推荐做法是不要将业务模型发送到您的视图中......而应该创建特定于每个视图的视图模型。
When that is done and you call the ModelState.IsValid method in your controller you are effectively checking the validity of the viewmodel but not the business object.
完成后,您在控制器中调用ModelState.IsValid方法,您实际上是在检查viewmodel的有效性,而不是业务对象的有效性。
What is the conventional approach to dealing with this?
处理这个问题的传统方法是什么?
public class Person
{
public int ID {get; set;};
[Required]
public string Name {get; set;}
[Required]
public string LastName {get; set;}
public virtual ICollection<Exam> Exams {get; set;}
}
public class PersonFormViewModel
{
public int ID {get; set;};
[Required]
public string Name {get; set;}
[Required]
public string LastName {get; set;}
}
This is exactly what I have right now but Im not sure if the [Required] attribute should appear on both models or just the ViewModel or just the Business Model.
这正是我现在所拥有的,但我不确定[Required]属性是出现在两个模型上还是仅出现在ViewModel上,还是只出现在商业模型中。
Any tips on this issue are appreciatedd.
有关此问题的任何提示都表示赞赏。
More links to support my claim that it is a common good practice to always use view models.
更多链接支持我声称始终使用视图模型是一种常见的良好做法。
How to add validation to my POCO(template) classes
如何向我的POCO(模板)类添加验证
http://blogs.msdn.com/b/simonince/archive/2010/01/26/view-models-in-asp-net-mvc.aspx
5 个解决方案
#1
7
My preference is to do input validation on the view models, and business validation on the domain models.
我的偏好是在视图模型上进行输入验证,在域模型上进行业务验证。
In other words, any data annotations such as required fields, length validation, regex, etc should be done on your view models, and added to the model state when error occurs.
换句话说,任何数据注释(如必填字段,长度验证,正则表达式等)都应在视图模型上完成,并在发生错误时添加到模型状态。
And you'll probably have business/domain rules that rely on more than just a "form", so you should do that either in the domain models (execute the validation after they're mapped back), or with a service layer.
并且您可能拥有的业务/域规则不仅仅依赖于“表单”,因此您应该在域模型中执行此操作(在映射后执行验证)或服务层。
All our models have a method called "Validate", which we call in the services prior to persisting. They throw custom exceptions if they fail business validation, which gets caught by the controller and also added to the model state.
我们所有的模型都有一个名为“Validate”的方法,我们在持久化之前在服务中调用它。如果业务验证失败,它们会抛出自定义异常,这些异常会被控制器捕获并添加到模型状态。
May not be everyone's cup of tea, but it's consistent.
可能不是每个人的一杯茶,但它是一致的。
Example of business validation, as requested:
业务验证示例,如要求:
Here's an example of a domain model we have, which represents a generic "Post" (question, photo, video, etc):
这是我们拥有的域模型的示例,它代表一般的“帖子”(问题,照片,视频等):
public abstract class Post
{
// .. fields, properties, domain logic, etc
public void Validate()
{
if (!this.GeospatialIdentity.IsValidForThisTypeOfPost())
throw new DomainException(this, BusinessException.PostNotValidForThisSpatial.);
}
}
You see there, i am checking against business rules, and throwing custom exceptions. DomainException
is our base, and we have many derived implementations. We have an enum called BusinessException
, which contains values for all our exceptions. We use extension methods on the enum to provide the resource-based error message.
你看到那里,我正在检查业务规则,并抛出自定义异常。 DomainException是我们的基础,我们有许多派生的实现。我们有一个名为BusinessException的枚举,它包含所有异常的值。我们在枚举上使用扩展方法来提供基于资源的错误消息。
This is not simply a field on the model im checking, e.g "All posts must have a subject", because although that is part of the domain, it's input validation first and foremost, and thus is handled via the data annotations on the view model.
这不仅仅是模型im检查中的一个字段,例如“所有帖子必须有一个主题”,因为虽然这是域的一部分,但它首先是输入验证,因此通过视图模型上的数据注释来处理。
Now, the controller:
现在,控制器:
[HttpPost]
public ActionResult Create(QuestionViewModel viewModel)
{
if (!ModelState.IsValid)
return View(viewModel);
try
{
// Map to ViewModel
var model = Mapper.Map<QuestionViewModel,Question>(viewModel);
// Save.
postService.Save(model); // generic Save method, constraint: "where TPost: Post, new()".
// Commit.
unitOfWork.Commit();
// P-R-G
return RedirectToAction("Index", new { id = model.PostId });
}
catch (Exception exc)
{
var typedExc = exc as DomainException;
if (typedExc != null)
{
// Internationalised, user-friendly domain exception, so we can show
ModelState.AddModelError("Error", typedExc.BusinessError.ToDescription());
}
else
{
// Could be anything, e.g database exception - so show generic msg.
ModelState.AddModelError("Error", "Sorry, an error occured saving the Post. Support has been notified. Please try again later.");
}
}
return View(viewModel);
}
So, by the time we get to the "Save" method on the service, the model has passed input validation. Then the Save method calls post.Validate()
, invoking business rules.
因此,当我们在服务上使用“保存”方法时,模型已通过输入验证。然后Save方法调用post.Validate(),调用业务规则。
If an exception is raised, the controller catches it and displays the message. If it gets pass the Save method and another error occurs (90% of the time, it's Entity Framework, for example), we show a generic error message.
如果引发异常,控制器会捕获它并显示消息。如果它通过了Save方法并且发生了另一个错误(例如,90%的时间,它是实体框架),我们会显示一般错误消息。
As i said, not for everyone, but this works well for our team. We have a clear seperation of presentation and domain validation, and a consistent flow of control from the raw HTTP POST, to the redirect after success.
正如我所说,不是每个人都这样,但这对我们的团队来说很有效。我们清楚地分离了表示和域验证,以及从原始HTTP POST到成功后重定向的一致控制流。
HTH
#2
4
The MetaData "buddy" class is exactly what this is for. The validation is created once but can be used on both the model and the viewmodel classes:
MetaData“伙伴”类就是这样的。验证只创建一次,但可以在模型和viewmodel类上使用:
public class PersonMetaData
{
[Required]
public string Name {get; set;}
[Required]
public string LastName {get; set;}
}
[MetadataType(typeof(PersonMetaData))]
public class Person
{
public string Name {get; set;}
public string LastName {get; set;}
}
[MetadataType(typeof(PersonMetaData))]
public class PersonFormViewModel
{
public string Name {get; set;}
public string LastName {get; set;}
}
#3
2
Great answer by RPM1984, and nice code sample.
RPM1984的很棒的答案和漂亮的代码示例。
My view is still that using Variant 2 from the start is a pragmatic balance between productivity and structure, but you always have to be willing to move to Variant 3 in some cases, so I advocate a mix of the two approaches where logical. Patterns & Practices however recommend always doing Variant 3 as it is the ideal separation of concerns etc, and it avoids you using two approaches in the same solution which some people don't like and many customers I work with pick Variant 3 and run with that for all models.
我的观点仍然是从一开始就使用Variant 2是生产力和结构之间的务实平衡,但在某些情况下你总是愿意转向Variant 3,所以我提倡将这两种方法混合在一起。然而,模式和实践建议总是使用Variant 3,因为它是理想的关注点分离等,它避免了你在同一个解决方案中使用两种方法,有些人不喜欢和我工作的许多客户选择Variant 3并运行适用于所有型号。
I think the key is what RPM1984 said - reusing your business entities inside your View Models is useful for the sake of reusing the validation, but do bear in mind that often your business logic needs to do different validation too (e.g. checking the record doesn't already exist). If you use Variant 3 it empowers you to focus your View Model validation purely on the needs of your views (at the expense of a little extra effort) but you will always need some kind of business logic validation too.
我认为关键是RPM1984所说的 - 在View模型中重用您的业务实体对于重用验证很有用,但请记住,您的业务逻辑通常也需要进行不同的验证(例如,检查记录没有'已经存在)。如果您使用Variant 3,它使您能够将View模型验证完全集中在视图的需求上(以一点点额外的努力为代价),但您也总是需要某种业务逻辑验证。
#4
1
Given that you always pass viewmodels to your view and your domain models are not exposed to the end user through views then i don't see any need for validating domain model itself. For example if you receive PersonViewModel from view through form post you will transform it to Person Model (perhaps through automapper etc.) only if PersonViewModel is valid itself. So i believe that input validations should stay with the view models because they are the one that are bound to input.
鉴于您始终将视图模型传递给视图,并且您的域模型未通过视图向最终用户公开,那么我认为不需要验证域模型本身。例如,如果从视图到表单发布接收PersonViewModel,只有当PersonViewModel本身有效时,您才会将其转换为Person Model(可能通过automapper等)。所以我认为输入验证应该与视图模型保持一致,因为它们是绑定输入的。
#5
0
Typically your ViewModel will contain a reference to your Model - a ViewModel is only necessary if you need to display additional information in your view that isn't available in your Model, there is no need to replicate the data already present.
通常,ViewModel将包含对模型的引用 - 仅当您需要在视图中显示模型中不可用的其他信息时才需要ViewModel,无需复制已存在的数据。
#1
7
My preference is to do input validation on the view models, and business validation on the domain models.
我的偏好是在视图模型上进行输入验证,在域模型上进行业务验证。
In other words, any data annotations such as required fields, length validation, regex, etc should be done on your view models, and added to the model state when error occurs.
换句话说,任何数据注释(如必填字段,长度验证,正则表达式等)都应在视图模型上完成,并在发生错误时添加到模型状态。
And you'll probably have business/domain rules that rely on more than just a "form", so you should do that either in the domain models (execute the validation after they're mapped back), or with a service layer.
并且您可能拥有的业务/域规则不仅仅依赖于“表单”,因此您应该在域模型中执行此操作(在映射后执行验证)或服务层。
All our models have a method called "Validate", which we call in the services prior to persisting. They throw custom exceptions if they fail business validation, which gets caught by the controller and also added to the model state.
我们所有的模型都有一个名为“Validate”的方法,我们在持久化之前在服务中调用它。如果业务验证失败,它们会抛出自定义异常,这些异常会被控制器捕获并添加到模型状态。
May not be everyone's cup of tea, but it's consistent.
可能不是每个人的一杯茶,但它是一致的。
Example of business validation, as requested:
业务验证示例,如要求:
Here's an example of a domain model we have, which represents a generic "Post" (question, photo, video, etc):
这是我们拥有的域模型的示例,它代表一般的“帖子”(问题,照片,视频等):
public abstract class Post
{
// .. fields, properties, domain logic, etc
public void Validate()
{
if (!this.GeospatialIdentity.IsValidForThisTypeOfPost())
throw new DomainException(this, BusinessException.PostNotValidForThisSpatial.);
}
}
You see there, i am checking against business rules, and throwing custom exceptions. DomainException
is our base, and we have many derived implementations. We have an enum called BusinessException
, which contains values for all our exceptions. We use extension methods on the enum to provide the resource-based error message.
你看到那里,我正在检查业务规则,并抛出自定义异常。 DomainException是我们的基础,我们有许多派生的实现。我们有一个名为BusinessException的枚举,它包含所有异常的值。我们在枚举上使用扩展方法来提供基于资源的错误消息。
This is not simply a field on the model im checking, e.g "All posts must have a subject", because although that is part of the domain, it's input validation first and foremost, and thus is handled via the data annotations on the view model.
这不仅仅是模型im检查中的一个字段,例如“所有帖子必须有一个主题”,因为虽然这是域的一部分,但它首先是输入验证,因此通过视图模型上的数据注释来处理。
Now, the controller:
现在,控制器:
[HttpPost]
public ActionResult Create(QuestionViewModel viewModel)
{
if (!ModelState.IsValid)
return View(viewModel);
try
{
// Map to ViewModel
var model = Mapper.Map<QuestionViewModel,Question>(viewModel);
// Save.
postService.Save(model); // generic Save method, constraint: "where TPost: Post, new()".
// Commit.
unitOfWork.Commit();
// P-R-G
return RedirectToAction("Index", new { id = model.PostId });
}
catch (Exception exc)
{
var typedExc = exc as DomainException;
if (typedExc != null)
{
// Internationalised, user-friendly domain exception, so we can show
ModelState.AddModelError("Error", typedExc.BusinessError.ToDescription());
}
else
{
// Could be anything, e.g database exception - so show generic msg.
ModelState.AddModelError("Error", "Sorry, an error occured saving the Post. Support has been notified. Please try again later.");
}
}
return View(viewModel);
}
So, by the time we get to the "Save" method on the service, the model has passed input validation. Then the Save method calls post.Validate()
, invoking business rules.
因此,当我们在服务上使用“保存”方法时,模型已通过输入验证。然后Save方法调用post.Validate(),调用业务规则。
If an exception is raised, the controller catches it and displays the message. If it gets pass the Save method and another error occurs (90% of the time, it's Entity Framework, for example), we show a generic error message.
如果引发异常,控制器会捕获它并显示消息。如果它通过了Save方法并且发生了另一个错误(例如,90%的时间,它是实体框架),我们会显示一般错误消息。
As i said, not for everyone, but this works well for our team. We have a clear seperation of presentation and domain validation, and a consistent flow of control from the raw HTTP POST, to the redirect after success.
正如我所说,不是每个人都这样,但这对我们的团队来说很有效。我们清楚地分离了表示和域验证,以及从原始HTTP POST到成功后重定向的一致控制流。
HTH
#2
4
The MetaData "buddy" class is exactly what this is for. The validation is created once but can be used on both the model and the viewmodel classes:
MetaData“伙伴”类就是这样的。验证只创建一次,但可以在模型和viewmodel类上使用:
public class PersonMetaData
{
[Required]
public string Name {get; set;}
[Required]
public string LastName {get; set;}
}
[MetadataType(typeof(PersonMetaData))]
public class Person
{
public string Name {get; set;}
public string LastName {get; set;}
}
[MetadataType(typeof(PersonMetaData))]
public class PersonFormViewModel
{
public string Name {get; set;}
public string LastName {get; set;}
}
#3
2
Great answer by RPM1984, and nice code sample.
RPM1984的很棒的答案和漂亮的代码示例。
My view is still that using Variant 2 from the start is a pragmatic balance between productivity and structure, but you always have to be willing to move to Variant 3 in some cases, so I advocate a mix of the two approaches where logical. Patterns & Practices however recommend always doing Variant 3 as it is the ideal separation of concerns etc, and it avoids you using two approaches in the same solution which some people don't like and many customers I work with pick Variant 3 and run with that for all models.
我的观点仍然是从一开始就使用Variant 2是生产力和结构之间的务实平衡,但在某些情况下你总是愿意转向Variant 3,所以我提倡将这两种方法混合在一起。然而,模式和实践建议总是使用Variant 3,因为它是理想的关注点分离等,它避免了你在同一个解决方案中使用两种方法,有些人不喜欢和我工作的许多客户选择Variant 3并运行适用于所有型号。
I think the key is what RPM1984 said - reusing your business entities inside your View Models is useful for the sake of reusing the validation, but do bear in mind that often your business logic needs to do different validation too (e.g. checking the record doesn't already exist). If you use Variant 3 it empowers you to focus your View Model validation purely on the needs of your views (at the expense of a little extra effort) but you will always need some kind of business logic validation too.
我认为关键是RPM1984所说的 - 在View模型中重用您的业务实体对于重用验证很有用,但请记住,您的业务逻辑通常也需要进行不同的验证(例如,检查记录没有'已经存在)。如果您使用Variant 3,它使您能够将View模型验证完全集中在视图的需求上(以一点点额外的努力为代价),但您也总是需要某种业务逻辑验证。
#4
1
Given that you always pass viewmodels to your view and your domain models are not exposed to the end user through views then i don't see any need for validating domain model itself. For example if you receive PersonViewModel from view through form post you will transform it to Person Model (perhaps through automapper etc.) only if PersonViewModel is valid itself. So i believe that input validations should stay with the view models because they are the one that are bound to input.
鉴于您始终将视图模型传递给视图,并且您的域模型未通过视图向最终用户公开,那么我认为不需要验证域模型本身。例如,如果从视图到表单发布接收PersonViewModel,只有当PersonViewModel本身有效时,您才会将其转换为Person Model(可能通过automapper等)。所以我认为输入验证应该与视图模型保持一致,因为它们是绑定输入的。
#5
0
Typically your ViewModel will contain a reference to your Model - a ViewModel is only necessary if you need to display additional information in your view that isn't available in your Model, there is no need to replicate the data already present.
通常,ViewModel将包含对模型的引用 - 仅当您需要在视图中显示模型中不可用的其他信息时才需要ViewModel,无需复制已存在的数据。