使用View Model从表现层分离领域模型
Model-View-Controller(模型-视图-控制器,MVC) 模式将你的软件组织并分解成三个截然不同的角色:
-
Model 封装了你的应用数据、应用流程和业务逻辑。
-
View 从 Model 获取数据并格式化数据以进行显示。
-
Controller 控制程序流程,接收输入,并把它们传递给 Model 和 View。
与其它设计模式不同,MVC 模式并没有直接反映一个你能够编写或配置的类结构。相反,MVC 更像一个概念上的指导原则或范型。概念上的 MVC 模式被描述为三个对象 —— Model、View 和 Controller —— 之间的关系。由于 View 和 Controller 都可以从 Model 请求数据,所以 Controller 和 View 都依赖 Model。任何输入都通过 Controller 进入你的系统,然后 Controller 选择一个 View 来发出结果。
Model 包含了你的应用逻辑和数据,在你的应用程序中,它很可能是主要的值驱动器。Model 没有任何与表现层相关的特性,而且也和 HTTP 请求处理职责中完全无关。
Domain Model 是一个对象层,是对现实世界逻辑、数据和你应用程序所处理的问题的抽象。Domain Model 可分为两大类:Simple Domain Model 和 Rich Domain Model。
Simple Domain Model 往往是业务对象和数据库表之间一对一的通信。你已经见过的几种模式 —— Active Record、Table Data Gateway,以及 Data Mapper,所有这些与数据库相关的设计模式 —— 可以帮助你把与数据库相关的逻辑组织成一个 Domain Model。
Rich Domain Model 包含复杂的,使用继承机制紧密联系在一起的对象网络,在本书和 GoF 一书中介绍的众多模式起着杠杆作用。Rich Domain Models 往往是柔性的,精心测试过的,不断重构的,而且与它们所表达的领域所需的业务逻辑紧密耦合。
采用哪种 Domain Model 类型取决于你的应用环境。如果你正在建立的是一个非常简单的表单处理 web 应用,没必要建立 Rich Domain Model。然而,如果你正在编写一个价值数百万的企业内联网架构的核心库,那么努力开发一个 Rich Domain Model 就是值得的,它可以为你提供一个准确表达业务过程的平台,并可以让你快速传输数据。
Martin Fowler 在 PoEAA 中同时简要介绍了两种 Domain Model。而 Eric Evans 的 Domain Driven Design 一书,则完全专注于 Rich Domain Model 的实践应用和开发过程。
View 用于处理所有表现层方面的问题。View 从 Model 获取数据,并可以把它格式化成用于 web 页的 HTML,用于 web 服务的 XML,或用于 email 的文本。
许多的MVC模式的实现也都使用一个View Model或Application Model的概念,Controller是沟通的媒介,架起领域模型和用户界面之间的桥梁,属于表现层。为了View的简单性,Controller负责处理或者将领域模型转换成一个View Model,这通常叫做数据传输对象(DTO)。
<译>12个asp.net MVC最佳实践针对Model的最佳实践有这么一段:
7–DomainModel != ViewModel
DomainModel代表着相应的域,但ViewModel却是为View的需要而创建。这两者之间或许(一般情况下都)是不同的,此外DomainModel是数据加上行为的组合体,是由复杂的变量类型组成的并且具有层次。而ViewModel只是由一些String等简单变量类型组成。如果想移除冗余并且容易导致出错的ORM代码,可以使用AutoMapper.如果想要了解更多,我推荐阅读:ASP.NET MVC View Model Patterns.
那么领域模型(Domain Model )和视图模型(View Model)有什么不同呢?
在ASP.NET MVC的应用程序中经常可以可以看到View Model,经常我们都认为领域模型和视图模型是同一个东西。这特别是把领域模型包含在数据传输对象DTO里的时候,例如使用Entity Framework之类的ORM工具生成的实体。在这种情况下,领域模型和视图模型包含的实体非常相似,都是一些简单的CRUD操作。
这些实体有许多属性,有相同或类似的名称,你可以很容易地映射领域实体对应视图模型中的一个属性。不过,这些相似的属性也可能略有不同,例如类型或者格式。例如,用户填写的用户界面的一个属性,他在视图模型里可能是一个“Nullable”的。另一方面,领域实体可能需要一个经过验证的合法的值,所以需要一个在用户界面的领域模型之间的转换。另一个例子是,用户界面可能会显示一个滑块,用于用户选择多少天以后提交他的订单。在这种情况下,视图模型可能使用一个整数属性来表示,领域模型通常是一个日期值。
视图模型通常只包含领域模型的一个子集,而且只包含界面上所需要的属性。此外,视图模型可能是一个领域模型树的扁平版本,例如,一个Customer实体有一个Address,而这又是一个整体,它包含街道地址,邮政编码,国家等。一个Customer 视图模型用于显示数据,将地址数据拉平填充到视图模型类里。
此外如果一个View需要同时处理几个领域模型,View Model就是这几个Domain Model的总和。领域模型和视图模型之间有很多相似的地方,我们经常干脆就把Domain Model当作View Model来使用了。
上面讨论了领域模型和视图模型的相似性,我们来看看都有几种方式把领域模型转换为视图模型,通常有3种方法:
- 把领域模型当作视图模型来用,也就是领域模型就是视图模型,大部分都是这么用的。
- 视图模型里面包含一个领域模型,定义一个视图模型,里面包含了一个领域模型,通过属性方式进行访问。
- 将领域模型映射到视图模型,领域模型并没有直接映射到视图模型,需要处理这种映射关系。
我们不建议直接把领域模型实体暴露给视图,因为有许多细微之处,可能导致您混合业务和表示层的逻辑,无论是领域实体的属性显示还是业务的验证规则,这都是应用程序处理的不同方面。直接将你的领域模型作为Conroller上的处理参数面临着安全风险,因为Controller或者Model binder必须确保属性验证和用户不能修改她自己不能修改的属性(例如,用户手动更新了一个隐藏的输入值,或增加一个额外的属性值,而这个并不是界面上的元素,但却正好领域模型实体的属性,这种风险叫做“over-posting”),即使对当前版本的领域模型做了正确的验证,领域模型将来可能做了变更修改,并没有出现编译错误或者警告,可能导致新的风险。
我们应当避免使用前两种方法将领域模型转换成视图模型,推荐使用第三种方法,定义单独的视图模型类。做这种领域模型到视图模型的转换工作是一种重复性的工作,已经有几个工具可以帮助你来完成这项工作。最常用的一个工具就是.NET 社区的开源项目AutoMapper。
如何使用AutoMapper可以参考下面的两篇文章介绍: