MVC 验证 - 使用服务层保持 DRY - 最佳实践是啥?

Posted

技术标签:

【中文标题】MVC 验证 - 使用服务层保持 DRY - 最佳实践是啥?【英文标题】:MVC Validation - Keep it DRY with a service layer - What is best practice?MVC 验证 - 使用服务层保持 DRY - 最佳实践是什么? 【发布时间】:2011-12-25 20:23:23 【问题描述】:

我正在尝试遵守最佳的多层设计实践,并且不希望我的 MVC 控制器与我的 DAL(或任何 IRepository)进行交互。它必须通过我的业务服务层来执行正确的业务规则和验证。验证 - 我不想在我的域模型实体上使用各种验证属性(例如 [必需])在控制器中执行验证,因为这会照亮我的前端。更不用说这个服务也可以通过 WPF 前端来实现。

由于我的验证是在我的服务层中完成的,将值返回到 UI 的最佳做法是什么?我不想要'void addWhatever(int somethingsID)',因为我需要知道它是否失败。它应该是一个布尔值吗?它应该是一个枚举吗?我应该利用异常处理吗?或者我应该在将验证属性装饰到模型对象时返回一些类似于 MVC 使用的 IValidationDictionary 对象? (如果需要,我可以稍后在 UI 中使用适配器模式)

我想将我的实体从控制器传递到服务层,并了解验证/数据持久性是否失败。我也不想忽略这样一个事实,即我需要返回一个视图,以指示每个可能未通过验证的字段的正确错误消息(我希望尽可能轻松地做到这一点)。

我有几个想法,但都觉得不对劲。我觉得答案包括 View-specific-model 实体,但这会导致必须处理的整个映射问题,更不用说这违反了 DRY(不要重复自己)原则。最佳做法是什么?

【问题讨论】:

【参考方案1】:

这里的问题是服务层中的验证,以及如何将该信息“备份”到 Web 应用程序。 我们之前讨论过类似的东西,因为如果服务正在验证,依赖注入的想法在这里很明显会发挥作用,你不能在模型中简单地调用服务(例如,如果在那里实现了 IValidateableObject 你不想调用服务直接)

采取的方法是:

选项 3:我之前并不知道,但似乎是 编写验证器的非常强大的方法是使用 ModelValidator 类和相应的 ModelValidatorProvider。

ASP.NET MVC 3: Validating model when information external to the model is required

所以基本上你正在注入一个验证器(然后将在你的服务层中)由 mvc 解析,而不需要显式的服务定位器调用。

【讨论】:

【参考方案2】:

斯蒂芬建议在这种情况下是完美的。目前,我正在开发一个非常大的带有 SOA 的 MVC 3.0 应用程序,并且涉及到其他事情。因此,在响应中,您希望填写所有必要的信息并将它们显示给您的视图(当然控制器会指示)。希望这会有所帮助。

【讨论】:

【参考方案3】:

在几个层(客户端、控制器中的服务器端或等效层,以及再次在业务层中)重复运行验证实际上并不是一件坏事。它使您的代码有些解耦。理想情况下,您只需在一个地方描述它们,但有时这是不可能的。由于未能使用数据注释,如果您想进行客户端验证,您是否真的很难做到?好像是这样。

无论如何,我过去在非 mvc 应用程序中所做的是让大多数操作方法返回一个 Response 对象,其中包括状态(成功、错误、警告)和验证错误列表,以及任何其他所需的属性。

您也许可以利用 IValidateableObject 接口,但这又将您与 ASP.net 特定的东西联系在一起。也许妥协是使用您的响应对象并转换为特定于 DataAnnotation 的错误。

【讨论】:

【参考方案4】:

我知道,做 MVC 验证似乎违反了 DRY,但实际上.. 它并没有.. 至少对于大多数(非平凡的)应用程序来说不是。

为什么?因为您的视图的验证要求通常与您的业务对象验证要求不同。您的视图验证涉及验证特定视图是否有效,而不是您的业务模型是否有效。

有时这两者是相同的,但如果您构建应用程序以使视图要求业务模型有效,那么您将自己锁定在这种情况下。如果您需要将对象创建分成两个页面会发生什么?如果您决定将服务层用于 Web 服务,会发生什么?通过将您的 UI 锁定在业务层验证场景中,您会严重削弱您可以提供的各种解决方案。

视图是对输入的验证,而不是对模型的验证。

【讨论】:

尽管这是一个相当古老的问题,但我想对这个主题进行一些澄清。假设我需要验证 username 不应超过 15 个字符并且它应该是唯一的。 15 个字符的限制会发生在控制器中,检查唯一性会发生在服务中吗? @Aquillo - 不,您将在两个位置都进行验证。这个答案一般是关于视图和验证,而不是关于特定项目。 MVC 验证“模型”而不是属性,模型由 0 个或多个属性组成,这些属性可以具有 0 个或多个验证。 MVC 只说“这个模型有效吗”。【参考方案5】:

我建议您通过使用数据注释装饰您的模型类来利用内置的 MVC 验证。这仅适用于与处理业务规则和验证不同的基本输入验证。数据注释很棒,因为它们对任何有意识的消费者都很有用,但不会对不了解如何使用它们的消费者产生不利影响。

我认为您使用服务层来抽象业务规则和数据访问是正确的。您可能需要做一些事情来增强控制器和服务之间的交互:

    返回 XXXResult 对象而不是 void 或原语。如果您的服务方法是 AddProduct,则返回 AddProductResult 或更广泛的 ProductServiceOperationResult。此结果包含成功/失败指示符以及其他信息。

    如果您使用的是 WCF,请使用错误协定和异常。

我的一个典型的MVC应用方案是这样的:

MVC 网站项目 xxx.Model(项目,被大多数层引用) xxx.Services(项目) xxx.DataAccess(项目,有时与服务合并) 其他根据需要

祝你好运!

【讨论】:

【参考方案6】:

我就是这样做的。

让您的服务层在业务规则/验证规则失败时抛出异常。为此创建您自己的验证异常,并包含一些属性来保存验证错误的详细信息 - (例如,哪个属性有验证错误,以及消息是什么)

然后在 Exception 上创建一个扩展方法,它将错误的详细信息复制到 ModelState(我从 Steve Sandersons 相当优秀的“Pro Asp.Net MVC 2 Framework”一书中得到了这个想法)——如果你做对了,MVC将突出显示无效字段,在 UI 中显示错误等。

那么你的控制器将包含这样的东西

try

    Service.DoSomeThing();

catch (Exception err)

    err.CopyTo(ModelState);

这意味着您的业务规则和验证现在在您的服务层中,可以重复使用。

考虑同时将 DTO / 视图模型传递给您的视图,并将您的域对象映射到 DTO 和(反之亦然),而不是将您的域对象传递给您的视图。

然后 DTO / 视图模型可以驻留在 MVC 层中,您可以使用 Validation 属性来装饰它们,并让控制器将它们传递给视图 - 从而使用内置的 MVC 验证。

您会发现,对于任何复杂的项目,您在 UI 端所需的验证可能与您在业务规则端所需的验证略有不同,因此这有助于区分。

有一个名为AutoMapper 的好库,它可以轻松地从您的域对象映射到您的 DTO(反之亦然),而无需大量样板代码。

【讨论】:

嗯,我不确定无效数据是否“异常”并且值得例外。为什么选择异常而不是返回结果或使用内置验证接口? 我同意,更重要的是,MVC 层验证未捕获的任何无效数据异常的,并且被服务层捕获。我实际上将 NHibernate.Validator 用于事物的验证方面,这会在数据层中引发异常。我也在 MVC 层中使用内置的验证接口。这个想法是 MVC 层应该使用内置的数据注释捕获输入错误,但是如果无效数据确实通过了 MVC 层,服务层将抛出异常 好吧,这更有意义。就个人而言,我不那样做,但我看到你的澄清有好处。 我喜欢这个,除了它只会通知客户端第一次验证失败。如果请求中有一堆验证失败,客户端必须一次修复一个,并在每次修复后重新发送整个请求。

以上是关于MVC 验证 - 使用服务层保持 DRY - 最佳实践是啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Django 模型字段定义保持 DRY

JSR-303 Bean Validation 介绍及 Spring MVC 服务端验证最佳实践

控制器或服务层中的 Spring MVC 验证?

JSR-303 Bean Validation 介绍及 Spring MVC 服务端验证最佳实践

从 Spring MVC 控制器访问服务层

如何在 MVC 5 应用程序中存储用户数据? (使用 Windows 身份验证)