2011-12-13 131 views
14

我试图在我的应用程序中实现验证策略。我有一个MVC层,服务层,存储库和域POCO。现在在MVC层,我在我的视图模型上使用数据注释来验证用户输入,从而让我给用户快速反馈。在控制器中,在使用automapper设置域对象之前,我调用ModelState.IsValid来检查输入。服务层验证

这是我的麻烦所在。我将我的域对象传入需要根据业务规则进行验证的服务,但是如何将验证错误传递回控制器?我找到的示例执行以下操作之一:

  • 在服务层中抛出异常并捕获Contoller。但这似乎是错误的,肯定例外情况是例外,我们应该返回一些有意义的东西。
  • 使用ModelStateWrapper并将ModelStateDictionary注入到服务中。但是这种方法最终展现为循环依赖(控制器依赖于服务,服务依赖于控制器),这似乎是一种糟糕的代码异味。
  • 将验证方法添加到POCO。与此相关的问题是业务规则可能依赖于其他POCO对象,所以必须在可访问所需表和对象的服务中完成。

有没有更简单的方法我缺少?我已经看到很多关于这个问题的问题,但除了上面提到的问题外,没有具体的解决方案我在考虑在服务中进行验证的任何方法都可以传回一些我可以在控制器中使用的键/值对象,但我不确定这种策略是否会在稍后出现问题。

+0

您可能会发现以下答案有用:http://stackoverflow.com/a/4851953/29407 –

+0

感谢达林,这看起来很有前途,虽然它确实用于Exception方法。这是通常被接受的方式吗? – James

回答

13

我认为相当优雅的方法是在服务上有一个验证方法,该方法返回来自业务逻辑的模型错误字典。这样,没有将ModelState注入到服务中 - 该服务只是进行验证并返回任何错误。然后由控制器将这些ModelState错误重新合并到ViewData中。

所以服务验证方法可能如下:

public IDictionary<string, string> ValidatePerson(Person person) 
{ 
    Dictionary<string, string> errors = new Dictionary<string, string>(); 

    // Do some validation, e.g. check if the person already exists etc etc 

    // Add model erros e.g.: 
    errors.Add("Email", "This person already exists"); 
} 

然后你可以使用扩展方法在你的控制器到这些错误映射到ModelState中,是这样的:

public static class ModelStateDictionaryExtensions 
{ 
    public static void Merge(this ModelStateDictionary modelState, IDictionary<string, string> dictionary, string prefix) 
    { 
     foreach (var item in dictionary) 
     { 
      modelState.AddModelError((string.IsNullOrEmpty(prefix) ? "" : (prefix + ".")) + item.Key, item.Value); 
     } 
    } 
} 

而且您的控制器然后将使用:

ModelState.Merge(personService.ValidatePerson(person), ""); 
+0

这会在使用Service.Create()(例如)之前从控制器调用吗?但是如果Service.Create()有一些传入对象失败的专门业务逻辑会怎么样?我们将如何通知控制器?在我看来,每种服务方法都需要自己的独特验证,因此可以通知控制器问题。我想我的服务方法可以返回IDictionary? – James

+0

是的,您可以单独调用Validate,但也应该在create方法中从服务本身调用验证。这样,创建方法也可以返回错误字典。如果字典为空,则验证成功。 –

+0

我认为这可能是最好的选择。正如许多指南中所建议的那样,我不想没有很好的理由抛出异常。你自己用过这种方法吗?我主要关心的是,如果一个Service方法已经有一个返回类型,那么我将无法返回IDictionary。我想这就是为什么一些指南使用ModelStateWrapper。 – James

1

作为cre按照Ian的建议,你可以用一个接受函数的验证器来完成这个工作。

例如在服务层:

public void ValidateModel(Customer customer, Action<string, string> AddModelError) 
{ 
    if (customer.Email == null) AddModelError("Email", "Hey you forgot your email address."); 
} 

然后在您的控制器,您验证通过一个调用:

myService.ValidateModel(model, ModelState.AddModelError); 

或者说你想用你的验证在一个控制台应用程序而无需访问ModelStateDictionary,你可以做到这一点:

errors = new NameValueDictionary(); 
myService.ValidateModel(model, errors.Add); 

这两项工作的原因 ModelStateDictionary.AddModelError()NameValueDictionary.Add()Action<string, string>的方法签名匹配。