【问题标题】:Service Layer Validation服务层验证
【发布时间】:2012-01-19 05:49:48
【问题描述】:

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

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

  • 在服务层中抛出异常并在控制器中捕获。但这似乎是错误的,当然例外是针对特殊情况的,我们应该返回一些有意义的东西。
  • 使用 ModelStateWrapper 并将 ModelStateDictionary 注入到服务中。但是这种方法最终会展开成循环依赖(控制器依赖于服务,服务依赖于控制器),这似乎是一种不好的代码味道。
  • 向 POCO 添加验证方法。这样做的问题是业务规则可能依赖于其他 POCO 对象,因此这肯定应该在有权访问所需表和对象的服务中完成。

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

【问题讨论】:

标签: asp.net-mvc asp.net-mvc-3 validation


【解决方案1】:

作为 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&lt;string, string&gt;的方法签名。

【讨论】:

    【解决方案2】:

    我认为一个相当优雅的方法是在您的服务上使用一个 validate 方法,该方法从业务逻辑返回一个模型错误字典。这样,就不会向服务注入 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), "");
    

    【讨论】:

    • 在使用 Service.Create() 之前是否会从控制器调用(例如)?但是如果 Service.Create() 有一些特殊的业务逻辑,传入的对象失败了——我们将如何通知控制器?在我看来,每个服务方法都需要自己独特的验证,因此需要一种通知控制器问题的方法。我猜我的 Service 方法可能会返回 IDictionary?
    • 是的,您可以单独调用 Validate,但也应在 create 方法中从服务本身调用 validate。这样,create 方法也可以返回错误字典。如果字典为空,则验证成功。
    • 我认为这很可能是要走的路。正如许多指南中所建议的那样,我不想在没有充分理由的情况下抛出异常。你自己用过这个方法吗?我主要担心的是,如果 Service 方法已经有返回类型,那么我将无法返回 IDictionary。我想这就是为什么一些指南使用 ModelStateWrapper 的原因。
    • 是的,我明白你对返回类型的意思,但我不明白为什么 validate 或 create 需要一个。是的,我自己使用过它,到目前为止它对我来说效果很好!
    • 我目前的想法是结合使用这两种技术:让服务公开一个 validate 方法,该方法返回一个验证错误的 IDictionary。我的服务创建(或其他)方法将返回 void(或其他),但它也会调用 validate 方法作为其过程的一部分 - 如果验证失败,则 create 将引发异常。这样调用者可以在调用 create 之前先检查 POCO 是否有效。如果他们不这样做,则可能是例外!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-23
    • 1970-01-01
    • 1970-01-01
    • 2010-11-01
    • 2017-10-19
    • 2017-01-02
    • 2012-10-25
    相关资源
    最近更新 更多