2009-10-28 78 views
5

我对用于在asp.net mvc中进行业务规则验证的方法有疑问。在Asp.net MVC中使用异常验证业务逻辑

目前,我有一个异常类,看起来像这样

public class ValidationException : Exception 
{ 
    private ModelStateDictionary State { get; set; } 
    public ValidationException(ModelStateDictionary state) 
    { 
     State = state; 
    } 
    public void MergeModelStates(ModelStateDictionary state) 
    { 
     state.Merge(this.State); 
    } 
} 

和看起来像这样

public void Validate(IEntity entity) 
{ 
    ModelStateDictionary state = new ModelStateDictionary(); 
    if (entity.Contact != null && _service.GetBy(entity.Contact.Id) == null) 
     state.AddModelError("Contact", "Invalid Contact."); 
    if (entity.Title.Length > 8) 
     state.AddModelError("title", "Title is too long..."); 
    ... etc 
    if (!state.IsValid) 
     throw new ValidationException(state); 
} 

验证器和控制器,做这样的事情

public ActionResult Add() 
{ 
    var entity = new InputModel; 
    try 
    { 
     TryUpdateMode(inputModel); 
     ..... Send input to a Repository (Repository calls Validate(entity); 
    } 
    catch (ValidationException validationException) 
    { 
     validationException.MergeModelStates(this.ModelState); 
     TryUpdateModel(inputModel); 
     return View("Add",inputModel); 
    } 
    return View("List"); 
} 

使用异常来做这样的事情是错误的吗? 有更好方法的例子吗?我真的不想将验证添加到模型实体本身。我已经看到完成的唯一方法是将控制器模型状态注入到存储库层,但对我来说似乎很渺茫。

感谢所有帮助

回答

13

例外一般应为特殊情况下,并没有处理的东西,可能你的程序的正常执行过程中经常发生。有很多很好的原因 - 这里的一些我已经相当经常碰到:

  1. 性能问题 - 例外通常是相当昂贵的操作 - 如果他们抛出定期性能可能会受到影响。
  2. 处理未捕获的验证异常 - 如果您碰巧使用您的代码而未处理异常,则会将验证错误显示为“黄色屏幕”或碰撞处理程序 - 可能不是最好的用户体验。
  3. 例外情况的结构不允许用户获得良好的信息。看一下异常类 - 没有太多的设置方式可以提供良好的面向用户的信息,这是将信息传递给用户所需的东西。任何时候我试图以这种方式使用异常,我已经结束了一大堆带有属性的子类,这些子类并没有真正意义上属于异常。

一种方法我平时喜欢做的是提供一个公共验证方法,它返回的错误的列表(但从来没有抛出异常本身),然后它调用验证(Save方法)和将引发异常有任何错误。如果将行为从“抛出,如果模型无效”切换到“抛出,如果代码在模型处于无效状态时尝试保存”。

为了解决下面有关在验证和保存中抛出的性能的评论 - 在Save()中抛出抛出Validate()将具有完全相同的性能损失。然而,关键的区别在于,这绝不会发生 - 您正在防范开发人员不恰当地使用您的类,而不是将异常用作验证方法。正确地编写,代码调用保存方法应该是这个样子:

ValidationResult result = obj.Validate(); 
if (result.IsValid) { 
    obj.Save(); 
} else { 
    // display errors to the user 
} 

如果开发商忘了保存前检查验证状态的异常只会被抛出。这有利于允许验证在不使用异常的情况下进行,并且通过永不允许保存无效实体来保护数据库。理想情况下,您不会在控制器中捕获异常,并让常规错误处理例程处理它,因为问题不再是用户输入,而是开发人员的错误。

+0

谢谢!这对我来说看起来更清洁,因为ModelStateDictionary不在除控制器之外的任何其他类中使用。 从Save方法抛出而不是从validate方法抛出性能差异? 哦,一旦异常被抛出并被捕获,我将需要调用验证并获取所有错误并将它们添加到模型状态? – AlteredConcept 2009-10-28 15:01:22

+0

好吧,我必须在控制器中再次验证一次,并再次在保存方法中验证。废话我想在存储库层做我所有的验证,但我想我也必须在控制器中做到这一点。哦,好吧..谢谢! – AlteredConcept 2009-10-28 19:51:54

2

理想情况下,你会使用数据批注功能在ASP.NET MVC 2: -

http://stephenwalther.com/blog/archive/2008/09/10/asp-net-mvc-tip-43-use-data-annotation-validators.aspx

http://weblogs.asp.net/scottgu/archive/2009/07/31/asp-net-mvc-v2-preview-1-released.aspx

例如: -

public class Person 
{ 
    [Required(ErrorMessage="Please enter a name.")] 
    public String Name { get; set; } 
} 

如果你没有升级,仍然有一个解决方案:

如果您向验证程序传递Controller的ModelState字典的引用(如果担心问题分离,请将其包含在接口中),如果发现错误,则调用AddModelError,那么在Controller中可以调用if(ModelState。 IsValid的),并采取适当的行动: -

var entity = new InputModel(); 

TryUpdateModel(entity); 
MyValidator.Validate(entity, ModelState); 

if(ModelState.IsValid) { ... 

和您的验证是这样的: -

public void Validate(IEntity entity, ModelStateDictionary state) 
{ 
    if (entity.Contact != null && _service.ValidId(entity.Contact.Id) == null) 
    state.AddModelError("Contact", "Invalid Contact."); 
    // etc 
}