在asp.net mvc 3中,您将验证放在哪里



asp.net mvc中一个常见的推荐做法是,您不应该将业务模型发送到您的视图中。。相反,您应该创建特定于每个视图的视图模型。

完成后,您将调用ModelState。控制器中的IsValid方法有效地检查了视图模型的有效性,而不是业务对象的有效性。

处理这一问题的传统方法是什么?

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;}
}

这正是我现在所拥有的,但我不确定[Required]属性是应该同时出现在两个模型上,还是只出现在ViewModel上,或者只出现在Business Model上。

关于这个问题的任何提示都将不胜感激。

更多链接支持我的说法,即始终使用视图模型是一种常见的良好做法。

如何将验证添加到我的POCO(模板)类

http://blogs.msdn.com/b/simonince/archive/2010/01/26/view-models-in-asp-net-mvc.aspx

我的偏好是对视图模型进行输入验证,对域模型进行业务验证。

换句话说,任何数据注释,如必填字段、长度验证、regex等,都应该在视图模型上完成,并在出现错误时添加到模型状态中。

而且,您可能拥有的业务/域规则不仅仅依赖于一个"表单",因此您应该在域模型中(在它们映射回后执行验证)或在服务层中这样做。

我们所有的模型都有一个名为"Validate"的方法,我们在持久化之前在服务中调用它。如果业务验证失败,它们会抛出自定义异常,控制器会捕获这些异常并将其添加到模型状态中。

可能不是每个人都喜欢,但这是一致的。

根据要求的业务验证示例:

这里有一个领域模型的例子,它代表了一个通用的"帖子"(问题、照片、视频等):

public abstract class Post
{
   // .. fields, properties, domain logic, etc
   public void Validate()
   {
      if (!this.GeospatialIdentity.IsValidForThisTypeOfPost())
         throw new DomainException(this, BusinessException.PostNotValidForThisSpatial.);
   }
}

你看,我正在检查业务规则,并抛出自定义异常。DomainException是我们的基础,我们有许多派生实现。我们有一个名为BusinessException的枚举,它包含所有异常的值。我们在枚举上使用扩展方法来提供基于资源的错误消息。

这不仅仅是我正在检查的模型上的一个字段,例如"所有帖子都必须有一个主题",因为尽管这是域的一部分,但它首先是输入验证,因此通过视图模型上的数据注释来处理。

现在,控制器:

[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);
}

因此,当我们在服务上使用"Save"方法时,模型已经通过了输入验证。然后Save方法调用post.Validate(),调用业务规则。

如果引发异常,控制器将捕获该异常并显示消息。如果它通过了Save方法,并且发生了另一个错误(例如,90%的时间是实体框架),我们将显示一条通用错误消息。

正如我所说,不是每个人都适用,但这对我们的团队来说效果很好。我们对表示和域验证有着明确的分离,从原始HTTPPOST到成功后的重定向都有着一致的控制流。

MetaData"buddy"类正是它的用途。验证创建一次,但可以在模型和视图模型类上使用:

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;} 
}

RPM1984的好答案,以及不错的代码示例。

我的观点仍然是,从一开始就使用变体2是生产力和结构之间的一种务实的平衡,但在某些情况下,您必须始终愿意使用变体3,因此我主张在合乎逻辑的情况下混合使用这两种方法。模式&然而,实践建议始终使用变体3,因为它是关注点等的理想分离,并且它避免了在同一解决方案中使用两种方法,而有些人不喜欢这种方法,我合作的许多客户都选择了变体3,并在所有模型中使用它。

我认为关键是RPM1984所说的——为了重用验证,在视图模型中重用您的业务实体是有用的,但请记住,您的业务逻辑通常也需要进行不同的验证(例如,检查记录不存在)。如果您使用变体3,它使您能够将视图模型验证完全集中在视图的需求上(以付出一点额外的努力为代价),但您也总是需要某种业务逻辑验证。

假设您总是将视图模型传递给视图,并且您的域模型不会通过视图向最终用户公开,那么我认为没有必要验证域模型本身。例如,如果您通过表单post从视图接收到PersonViewModel,则只有在PersonViewModel本身有效的情况下,您才会将其转换为PersonModel(可能通过automapper等)。因此,我认为输入验证应该留在视图模型中,因为它们是绑定到输入的。

通常,您的ViewModel将包含对模型的引用-只有当您需要在视图中显示模型中不可用的附加信息时,才需要ViewModel,不需要复制已经存在的数据。

相关内容

  • 没有找到相关文章

最新更新