服务层是不是应该返回 MVC 应用程序的视图模型?

Posted

技术标签:

【中文标题】服务层是不是应该返回 MVC 应用程序的视图模型?【英文标题】:Should a service layer return view models for an MVC application?服务层是否应该返回 MVC 应用程序的视图模型? 【发布时间】:2011-03-01 10:31:05 【问题描述】:

假设您有一个 ASP.NET MVC 项目并正在使用服务层,例如在 asp.net 网站上的此联系人管理器教程中:http://www.asp.net/mvc/tutorials/iteration-4-make-the-application-loosely-coupled-cs

如果您的视图有视图模型,那么服务层是否适合提供每个视图模型?例如,在服务层代码示例中有一个方法

    public IEnumerable<Contact> ListContacts()
    
        return _repository.ListContacts();
    

如果您想要一个 IEnumerable,它应该放在服务层,还是在其他地方是“正确”的地方?

也许更合适的是,如果您为与 ContactController 关联的每个视图都有一个单独的视图模型,那么 ContactManagerService 是否应该有一个单独的方法来返回每个视图模型?如果服务层不合适,应该在哪里初始化视图模型对象以供控制器使用?

【问题讨论】:

您可能还想看看weblogs.asp.net/scottgu/archive/2009/04/28/… NerdDinner 教程是获取此信息的最糟糕的地方。它非常适合演示 MVC 功能,但它所暗示的架构很糟糕。 @Aaronaught 既然您显然是专家,那么您关于正确 MVC 架构的深入教程在哪里? 【参考方案1】:

我想这取决于您认为“服务”是什么。在单个类的上下文中,我从来没有真正喜欢过 service 这个术语。它非常模糊,并没有告诉您太多关于课程的实际目的。

如果“服务层”是物理层,比如网络服务,那么绝对不是; SOA 上下文中的服务应该公开域/业务操作,而不是数据和表示逻辑。但是,如果 service 只是被用作进一步封装的抽象概念,我认为按照您所描述的方式使用它没有任何问题。

只是不要混淆概念。如果您的服务处理视图模型,那么它应该是一个表示服务,并在实际模型之上分层,从不直接接触数据库或任何业务逻辑。 p>

【讨论】:

在示例中,“服务层”是控制器和存储库层之间的抽象,其声明的意图是在将数据传递到存储库之前进行验证。它还包含上面的 ListContacts() 方法以及 GetContact() 方法来获取控制器用于绑定视图的单个 Contact 对象。因此,听起来似乎适合所述服务层了解完全充实的控制器可能需要的视图模型列表,对吗? @erg39:如果您的服务正在进行验证,那么它基本上是模型的一部分。它不处理表示逻辑,不应与视图模型耦合。 @Aaronaught:你认为初始化视图模型的合适位置在哪里?它应该只是一个单独的抽象,比如“视图模型服务层”吗? @erg39:它通常是控制器逻辑的一部分。如果逻辑非常复杂,则通常将其委托给映射层(或 AutoMapper)。 将软件层描述为物理分离的意义不大。通过对 Web 服务的 SOAP 方法调用分离的层与相同的内存空间是不相关的,并且是架构细节。虽然它可能会影响层的拓扑,但它不应该影响逻辑分离。【参考方案2】:

不,我不这么认为。服务应该只关心问题域,而不是呈现结果的视图。返回值应该用域对象而不是视图来表示。

【讨论】:

【参考方案3】:

一般来说,不会。

视图模型旨在提供与视图之间的信息,并且应该特定于应用程序,而不是一般域。控制器应该协调与存储库、服务(我在这里对服务的定义做出一些假设)等的交互,并处理构建和验证视图模型,并且还包含确定要呈现的视图的逻辑。

通过将视图模型泄漏到“服务”层中,您正在模糊您的层,现在可能会将特定于应用程序和表示的内容与应该关注域级职责的内容混合在一起。

【讨论】:

Tom:在示例中,控制器的 Index() 方法构建了它从上面显示的 ListContacts() 方法返回的视图,该方法返回一个 IEnumberable,所以视图模型不是已经泄漏到“服务层”?这是一个糟糕的例子吗?如果是这样,初始化视图模型以供控制器的各种视图使用的合适位置在哪里? @erg39:Contact 不是域模型类型吗?如果是这样,那么没有。您可能有一些看起来像您的视图模型的联系人,但也可能有特定于单个视图或一组视图的附加信息。联系人不(或不应该)知道视图的任何特定细节。 @erg39:就初始化视图模型而言,控制器负责构建新的视图模型实例并验证/验证从视图接收到的视图模型。 谢谢大家,很有帮助 在大多数情况下,Service 应该使用`ViewModels` 还是应该在将 Post 数据传递给 Service 之前将其映射到 Controller 中的相应实体?【参考方案4】:

按照传统的方法或理论,ViewModel 应该是用户界面层的一部分。至少名字是这么说的。

但是当你开始使用实体框架、MVC、存储库等自己实现它时,你就会意识到别的东西。

必须有人将实体/数据库模型与 ViewModels 映射(最后提到的 DTO)。这应该在 [A] UI 层(由控制器)中完成,还是在 [B] 服务层中完成?

我选择选项 B。选项 A 是不可以的,因为几个实体模型组合在一起形成一个 ViewModel 很简单。我们可能不会将不必要的数据传递给 UI 层,而在选项 B 中,服务可以处理数据并在映射(到 ViewModel)后仅将所需/最少的数据传递给 UI 层。

但是,我们还是选择选项 A,将 ViewModel 放在 UI 层(而将实体模型放在 Service 层)。

如果Service层需要映射到ViewModel,那么Service层需要在UI层访问ViewModel。哪个图书馆/项目? Viewmodel 应该在 UI 层的一个单独的项目中,并且这个项目需要被 Service Layer 引用。如果 ViewModel 不在单独的项目中,则存在循环引用,所以不行。让服务层访问 UI 层看起来很尴尬,但我们仍然可以应付它。

但是,如果有另一个 UI 应用使用此服务怎么办?如果有移动应用程序怎么办? ViewModel 能有多大不同?服务应该访问同一个视图模型项目吗?所有 UI 项目都会访问同一个 ViewModel 项目还是拥有自己的项目?

在这些考虑之后,我的答案是将 Viewmodel 项目放在服务层中。无论如何,每个 UI 层都必须访问服务层!并且可能有很多相似的 ViewModel 都可以使用(因此映射对于服务层来说变得更容易)。这些天映射是通过 linq 完成的,这是另一个优点。

最后,还有关于 DTO 的讨论。还有关于 ViewModels 中的数据注释。带有数据注释的 ViewModel (Microsoft.Web.Mvc.DataAnnotations.dll) 不能驻留在服务层,而是驻留在 UI 层(但 ComponentModel.DataAnnotations.dll 可以驻留在服务层)。如果所有项目都在一个解决方案(.sln)中,那么放在哪一层都没有关系。在企业应用中,每一层都有自己的解决方案。

所以 DTO 实际上是一个 ViewModel,因为大多数情况下两者之间会有一对一的映射(比如使用 AutoMapper)。同样,DTO 仍然具有 UI(或多个应用程序)所需的逻辑,并且驻留在服务层中。而 UI 层 ViewModel(如果我们使用 Microsoft.Web.Mvc.DataAnnotations.dll)只是从 DTO 复制数据,并添加了一些“行为”/属性。

[现在这个讨论即将开始一个有趣的转折点……:I]

并且不要认为数据注释属性仅适用于 UI。如果您使用 System.ComponentModel.DataAnnotations.dll 限制验证 那么相同的 ViewModel 也可以用于前端和后端验证(从而删除 UI-residing-ViewModel-copy-of-DTO)。此外,属性也可以在实体模型中使用。例如:使用 .tt,可以使用验证属性自动生成实体框架数据模型,以在发送到后端之前执行一些 DB 验证,例如 max-length。这节省了从 UI 到后端的往返以进行验证。它还使后端能够出于安全原因重新验证。所以模型是一个独立的验证器,你可以轻松地传递它。另一个优点是,如果 DB 中的后端验证发生变化,那么 .tt(读取 DB 细节并为实体类创建属性)将自动选择它。这也会迫使 UI 验证单元测试失败,这是一个很大的优势(因此我们可以更正它并通知所有 UI/消费者,而不是意外忘记和失败)。是的,讨论正在朝着良好的框架设计方向发展。如您所见,它们都是相关的:分层验证、单元测试策略、缓存策略等。

虽然与问题没有直接关系。必看channel 9 link中提到的“ViewModel Façade”也值得探索。它正好在视频中的 11 分 49 秒开始。因为一旦解决了上面给出的当前问题,这将是下一步/想法:“如何组织 ViewModel?”

最后,这些模型与逻辑问题中的许多问题都可以通过 REST 解决。因为每个客户端都可以智能地查询数据并只获取它需要的数据。并且它将模型保留在 UI 中,没有服务器/服务层模型/逻辑。唯一的重复将是每个客户需要执行的自动化测试。此外,如果数据发生变化,那么一些客户端如果不适应变化就会失败。那么问题是,您是完全删除服务层(以及它们携带的模型)还是将服务层推送到调用 REST API 的 UI 项目(因此模型问题仍然存在)。但就Service层的职责而言,无论如何都是一样的。

此外,在您的示例中,“_repository.ListContacts()”正在从存储库返回一个 ViewModel。这不是一种成熟的方式。存储库应提供实体模型或数据库模型。这被转换为视图模型,服务层返回的正是这个视图模型。

【讨论】:

在这里引入应用层不正确吗?它将映射视图模型和域实体,保持彼此独立和快乐。 视图模型和域实体无论如何都是独立的。 “应用层”是一个新层,只是为了映射实体?您是否还考虑过访问此应用程序层的 UI 层和服务?因为最后他们仍然需要访问他们的模型。或者换句话说,一个额外的层会解决什么问题? 多么棒的解释……完美。这意味着服务层可以返回视图模型,也可以返回 DTO。因为我们只会在需要合并多个 DTO 时创建 ViewModel。我说得对吗@BlueClouds? @AnkushJain “我们只会在需要合并多个 DTO 时创建 ViewModel” 不,我们会在视图需要时创建 ViewModel。 DTO 是从 DB 对象到 ViewModel 的载体(数据传输对象)。 ViewModel 可以是 DTO 的副本。 “而 UI 层 ViewModel(如果我们使用 Microsoft.Web.Mvc.DataAnnotations.dll)只是从 DTO 复制数据,并添加了一些'行为'/属性。”如果我们不在视图模型中使用特定于 UI 的属性/代码,那么我们根本不需要视图模型,我们可以使用 DTO 本身作为视图模型。从这个角度来看,合并多个 DTO 将成为另一个新的 DTO。 @AnkushJain 还俱乐部逻辑驻留在服务中(而不是外部)。首先,我们需要为每一层分配职责。合并多个服务调用是一项重要的职责,是您的视图在做,还是服务层在做?【参考方案5】:

这有点“取决于”我在哪里工作——我们通常有一个控制器使用一些服务——然后将返回的 DTO 组合成一个“ViewModel”,然后传递给客户端——要么通过 JSON 结果,或绑定在 Razor 模板中。

事情是,大约 80% 的时间 - DTO 到 ViewModel 的映射是 1-1。我们开始转向“在需要的地方,直接使用 DTO,但是当 DTO 与我们在客户端/视图中需要的内容不匹配时 - 然后我们创建一个 ViewModel 并根据需要在对象之间进行映射”。

虽然我仍然不相信这是最好或正确的解决方案 - 因为它最终导致了一些激烈的讨论“我们是否只是将 X 添加到 DTO 以满足视图的需求?”

【讨论】:

如果您将 DTO 保留在 Service 层并将其用作 UI 中的 ViewModel,您在哪里放置验证(DataAnnotation、FluentValidation 或其他)?我处于“80% 1:1 DTO 到 VM”的相同情况,所以不知道该怎么办。【参考方案6】:

您好,我在这里看到了很好的答案。 对于我自己,我采取了另一种方法。 我有各种模型,一种是视图模型,另一种是共享模型。视图模型保留在 UI 层,共享模型保留在单独的项目中。 共享模型理论上可以用于任何软件,因为它们是独立的。 如果您想从服务层返回特定数据,或者如果您需要从存储库中获取特定数据,此模型提供了一些抽象。 我真的不知道这是否是一个好的方法,但它在我的项目中效果很好。例如 当我需要提供一些信息来为数据库创建新对象时,我可以将共享模型直接用于服务层,这为我节省了一些复杂性。 共享模型有时需要映射,但您可以确保您的服务不会将不必要的数据泄漏到 UI。 您可以将共享模型视为扩展,但不能使用它构建您的 UI 逻辑,您应该有视图模型来执行此操作。 您可以将视图模型与此共享模型结合起来,以节省您可以使用继承的时间。 共享模型必须是中立的,不应该有任何逻辑。

【讨论】:

以上是关于服务层是不是应该返回 MVC 应用程序的视图模型?的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有模型的情况下验证 MVC3 中的强类型视图

ASP MVC:服务应该返回 IQueryable 的吗?

MVC的秘密

您是不是应该从业务层(或服务层、域模型等)返回 BindingList?

剃刀页面是不是位于 mvc 层之上?

在模型层 (MVC) 上实现应用程序逻辑