【问题标题】:Where do you put your validation in asp.net mvc 3?你在哪里把你的验证放在 asp.net mvc 3 中?
【发布时间】:2011-09-27 01:24:06
【问题描述】:

asp.net mvc 中一个常见的推荐做法是 you should not send your business models to your views.. 而您应该创建特定于每个视图的视图模型。

完成后,您在控制器中调用 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 上还是只出现在业务模型上。

感谢您提供有关此问题的任何提示。

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

How to add validation to my POCO(template) classes

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

【问题讨论】:

  • 你的“商业模式”到底是什么?
  • Person 是一个由 EntityFramework 跟踪的类。 PersonViewModel 显然不是……阅读我链接的博客文章,您将了解我试图遵循的做法……因此验证逻辑应该去哪里的问题。
  • 谢谢 - 抱歉,我扫描了这篇文章并在其中搜索了“商业模式”,但没有找到任何结果。
  • 这可能不是阅读文章的最佳方式......无论如何。 blogs.msdn.com/b/simonince/archive/2010/01/26/…

标签: asp.net-mvc validation viewmodel


【解决方案1】:

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

换句话说,任何数据注释,如必填字段、长度验证、正则表达式等都应该在您的视图模型上完成,并在发生错误时添加到模型状态中。

而且您的业务/域规则可能不仅仅依赖于“表单”,因此您应该在域模型中(映射回后执行验证)或使用服务来执行此操作层。

我们所有的模型都有一个名为“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 方法调用post.Validate(),调用业务规则。

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

正如我所说,不是每个人都适合,但这对我们的团队很有效。我们有一个清晰的展示和域验证分离,以及从原始 HTTP POST 到成功后重定向的一致控制流。

【讨论】:

  • 你介意发布一个小例子吗?尤其是控制器方法。
  • @NachoF - 如你所愿,添加示例。
【解决方案2】:

元数据“伙伴”类正是它的用途。验证创建一次,但可用于模型和视图模型类:

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

【讨论】:

  • BUt 将整个业务类传递到视图并用作视图模型以生成可编辑数据并将其重新提交回控制器并将其传递到 ORM x 1000 视图中的性能影响是什么?分钟 VS 使用特定于视图的 ViewModel 并将该模型传递给业务以转换为正确的数据。数据总是很忙,但是伙伴类如何影响 viewEngine 中繁忙服务器的开销?当然这很容易,但破坏服务器也很容易,因为我们没有意识到这些操作的含义。
【解决方案3】:

RPM1984 的出色回答,以及不错的代码示例。

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

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

【讨论】:

  • 如果您没有什么新鲜的要说的话,您应该考虑使用评论而不是回答或点赞。
【解决方案4】:

鉴于您总是将视图模型传递给您的视图,并且您的域模型不会通过视图暴露给最终用户,那么我认为不需要验证域模型本身。例如,如果您通过表单发布从视图中接收 PersonViewModel,则仅当 PersonViewModel 本身有效时,您才会将其转换为 Person Model(可能通过 automapper 等)。所以我相信输入验证应该保留在视图模型中,因为它们是绑定到输入的。

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 那么您的意思是,当您不需要业务对象本身不包含的额外信息时,您将业务模型对象传递给视图?
    • 正确,例如weblogs.asp.net/scottgu/archive/2010/01/15/…Scott Guthrie 将包含数据注释的 Person 类型传递给控制器​​中的视图。
    • 我刚刚阅读了您链接的文章,但在任何地方都看不到 Jimmy 所说的“您不应该将业务模型发送到您的视图”。事实上,文章中的任何地方都没有出现“不”这个词,Jimmy 说“当然,这些意见中的许多只在我们项目的限制条件下才真正有效”并且“这些意见可能适用于您的项目,也可能不适用从事于。同样,模式都是关于权衡、利益和责任的
    • 好吧,他说“我们从不将域模型实体直接传递到视图中。”....我的问题仍然是...鉴于我确实想使用这种方法..应该在哪里验证去吗?
    • 您添加的链接 (blogs.msdn.com/b/simonince/archive/2010/01/26/…) 支持我在变体 2:容器视图模型中描述的建议 - '总的来说,这是我个人最喜欢的,因为它提供了灵活性和未来的证明在变体 1' 之上的额外努力。如果您真的想采用变体 3 的“乌托邦实践”,那么我会使用 HitLikeAHammer 的方法。
    猜你喜欢
    • 2010-11-14
    • 1970-01-01
    • 2012-10-14
    • 2011-03-18
    • 1970-01-01
    • 2010-09-11
    • 1970-01-01
    • 1970-01-01
    • 2012-11-25
    相关资源
    最近更新 更多