你在哪里把你的验证放在asp.net mvc 3?

时间:2011-07-01 21:24:55

标签: asp.net-mvc validation viewmodel

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

完成后,您在控制器中调用ModelState.IsValid方法,您实际上是在检查viewmodel的有效性,而不是业务对象。

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

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

5 个答案:

答案 0 :(得分:7)

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

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

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

我们所有的模型都有一个名为“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到成功后重定向的一致控制流。

HTH

答案 1 :(得分:4)

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

答案 2 :(得分:2)

RPM1984的很棒的答案和漂亮的代码示例。

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

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

答案 3 :(得分:1)

鉴于您始终将视图模型传递给视图,并且您的域模型未通过视图向最终用户公开,那么我认为不需要验证域模型本身。例如,如果从视图到表单发布接收PersonViewModel,则只有在PersonViewModel本身有效时才会将其转换为Person Model(可能通过automapper等)。所以我认为输入验证应该与视图模型保持一致,因为它们是必须输入的。

答案 4 :(得分:0)

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