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

时间:2009-10-28 14:13:14

标签: asp.net-mvc validation

我对我在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");
}

使用异常来做这样的事情是不对的? 有更好的方法的例子吗?我真的不想将验证添加到模型实体本身。我看到它完成的唯一另一种方法是将Controllers ModelState注入到Repository层,但这对我来说似乎很草率。

感谢您的帮助

2 个答案:

答案 0 :(得分:13)

异常通常应该用于特殊情况,而不是处理在程序正常执行期间经常发生的事情。这有很多很好的理由 - 这里有一些我经常遇到的问题:

  1. 性能问题 - 例外通常是相当昂贵的操作 - 如果经常抛出你的表现可能会受到影响。
  2. 处理未被捕获的验证异常 - 如果您碰巧使用您的代码而不处理异常,您将验证错误,直到“黄色屏幕”或崩溃处理程序 - 可能不是最好的用户体验。
  3. 异常的结构不是允许面向用户的信息。看看异常类 - 没有多少设置方式可以提供良好的面向用户的信息,这是将信息转发回用户所需要的。每当我试图以这种方式使用异常时,我最终会得到一大堆具有属性的子类,并没有真正理解属于异常。
  4. 我通常喜欢的一种方法是提供一个公共Validate方法,它返回一个错误列表(但从不抛出异常本身),然后是一个调用Validate()的Save方法,如果有的话会抛出异常错误。您可以将行为从“如果模型无效时抛出”切换为“如果代码在模型处于无效状态时尝试保存则抛出”。

    要解决以下关于Validate vs. Save中投掷性能的评论 - 抛出Save()将与抛出Validate()具有完全相同的性能损失。然而,关键的区别在于,这应该永远不会发生 - 您正在防止使用您的类不正确的开发人员,而不是使用异常作为验证方法。正确编写,调用save方法的代码应该类似于:

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

    如果开发人员在保存之前忘记检查验证状态,则仅抛出异常。这样做的好处是既可以在不使用异常的情况下进行验证,也可以通过永不允许保存无效实体来保护数据库。理想情况下,您根本不会在控制器中捕获异常并让一般错误处理例程处理它,因为问题不再是用户输入而是开发人员的错误。

答案 1 :(得分: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字典的引用(如果您担心关注点分离,将其包含在接口内)到Validator,如果发现错误则调用AddModelError,然后在Controller中调用if( ModelState.IsValid)并采取适当的行动: -

var entity = new InputModel();

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

if(ModelState.IsValid) { ...

并且您的Validator看起来像: -

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