使用异常验证Asp.net MVC中的业务逻辑

7

我有一个关于在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");
}

使用异常来做这样的事情是否不妥?有更好的方法吗?我真的不想将验证添加到模型实体本身中。我看到的另一种方法是将控制器的ModelState注入存储库层,但那似乎很松散。

感谢任何帮助

2个回答

15

通常情况下,异常应该只处理非常例外的情况,而不是处理程序正常执行期间可能经常发生的事情。这样做有很多好的理由 - 这里是我经常遇到的一些:

  1. 性能问题 - 异常通常是非常昂贵的操作 - 如果它们经常被抛出,性能可能会受到影响。
  2. 处理未捕获的验证异常 - 如果您在不处理异常的情况下使用代码,您将把验证错误显示为“黄屏”或崩溃处理程序 - 这可能不是最好的用户体验。
  3. 异常没有结构化方式允许提供良好的面向用户的信息。查看异常类 - 并没有设置太多内容适合提供良好的面向用户的信息,这是您需要用于传达信息回到用户的内容。每次我尝试以这种方式使用异常时,都会出现许多子类和属性,这些属性并不真正属于异常。

我通常喜欢采用的一种方法是提供一个公共的Validate方法,它返回一个错误列表(但本身永远不会抛出异常),然后是一个Save方法,它调用Validate()并在有任何错误时抛出异常。您可以将行为从“如果模型无效,则抛出异常”切换为“如果代码尝试在模型处于无效状态时保存,则抛出异常”。

针对下面的评论,有关在Validate()和Save()中抛出异常的性能问题,两者的性能惩罚是完全相同的。然而,关键的区别是这不应该发生 - 您正在防范开发人员不正确使用您的类,而不是将异常用作验证方法。正确编写的调用Save方法的代码应如下所示:

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

只有在开发人员在保存之前忘记检查验证状态时,才会抛出该异常。这既允许进行验证而不使用异常的好处,又通过永远不允许保存无效实体来保护您的数据库。理想情况下,您根本不应该在控制器中捕获异常,而是让通用错误处理例程处理它,因为问题不再是用户输入而是开发人员的错误。


谢谢!对我来说,这似乎更清晰,因为ModelStateDictionary除了控制器之外没有在其他类中使用。 从Save方法抛出异常和从validate方法抛出异常之间是否存在性能差异? 哦,一旦抛出并捕获异常,我需要调用Validate并获取所有错误,并将它们添加到模型状态中,对吗? - AlteredConcept
好的,那么我需要进行两次验证,一次在控制器中,一次在保存方法中。糟糕,我本来想在存储库层中完成所有验证,但我想我也必须在控制器中进行验证。哦,没关系。谢谢! - AlteredConcept

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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接