原始 MVC 模式
MVC 模式的发明要追溯到 1979 年,当时将该模式作为一种远离全能、自治前端的方法。自治视图是一个类,可显示目录、维护视图的状态信息,并整合了整个逻辑可以始终处理任何用户操作。
此代码段为自治视图奠定了基础:
 
   void Button1_Click(object sender, EventArgs e)
   {
       // Perform any required action. All the code you 
       // need to handle the event goes here. A page 
       // built in this way is an autonomous view.
       // MVC fights autonomous views.
   }
使用这样的整体化视图,您几乎无法构建独立于基础层的可测试表示层。应用关注点分离 (SoC) 的概念可帮助您获取任一组件的低级耦合和高度聚合的正确组合,因为它使您可以将各种功能保留在其各自的层中,而不是将它们混合到一起。其次,应用 SoC,可以更轻松地实现导航工作流以确定接下来显示的页面。
通过联机方式可以阅读这篇介绍 MVC 方法的原创作品“ 如何使用模型-视图-控制器”。如果您尚未阅读此文章,我建议您无论是否熟悉 MVC 原理,都要看看这篇文章。现在,MVC 被认为是构建表示层的一种模式,但是它起初是用来构建完整应用程序的,从这个角度可以帮助您理解 MVC。
MVC 模式的定义并不严谨,因此实施细节时很多地方都需要架构师自行斟酌。这也许是为什么会存在这么多 MVC 变体的原因。
MVC 将应用程序拆分为三部分:模型、视图和控制器。模型引用应用程序数据,管理应用程序的功能并通知状态视图的更改。视图负责要向用户显示的内容。最后,控制器将用户操作映射到模型上的操作,然后更新当前视图或选择下一视图。这三个部分通常称为 MVC 的三层架构。 图 3 以图形方式显示了自治视图和 MVC 应用程序的比较结果。
图 3  从自治视图到 MVC
在 MVC 中,用户与视图交互,然后视图捕获操作(如按钮单击)并将其转发给控制器。控制器确定要执行的操作并通过与模型交互来执行该操作。此模型实际上就是业务层加上一些额外的功能。特别是,模型会通知视图更新 UI 时可能需要的更改。
虽然模型不了解视图的任何详细信息,但是模型和视图之间存在“观察者”关系。换句话说,视图保留对模型的引用并将其自身注册到模型以接收更改通知。接收到通知后,视图将从模型中获取新数据并对 UI 进行更新。 图 4 演示了 MVC 交互的顺序图。请注意,在一些  MVC 实现中,由控制器通知视图所做的更改。
图 4  标准 MVC 交互(单击图像可查看大图)

Model2:MVC 的 Web 变体
设计 MVC 的初衷是面向桌面应用程序,但是由于其设计比较松散,因此被稍加修改应用到了 Web 上。常用的 MVC 模式的 Web 变体是 Model2。Model2 是现在的 ASP.NET MVC Framework 使用的模式的曾用名。
Model2 模式通过浏览器向用户显示视图。用户操作由浏览器捕获,然后转换为新的 HTTP 请求。通过这种方式,请求从浏览器传到 Web 服务器中的特定组件(称为前端控制器)。前端控制器协调对 Web 应用程序提出的所有请求。
在 ASP.NET 中,此前端控制器采用 URL 路由 HTTP 模块这种形式。HTTP 模块捕获请求,然后将其路由到相应的控制器以执行请求操作。返回操作方法后,控制器将对视图进行排序以刷新并为新 UI 传递新数据。视图输出被前端控制器 HTTP 模块捕获,然后发送回浏览器。
图 5 显示典型 Model2 交互的顺序图。您应该注意到此图中仅包含常规步骤。例如,在该模式的 ASP.NET MVC 实现中,URL 路由组件除使用控制器外,还执行其他一些任务。
图 5  ASP.NET MVC Framework 中的 Model2 交互(单击图像可查看大图)
原始 MVC 和 Model2 有些差别。您可以看到,视图和模型之间没有任何联系,而在前面提到的 MVC 原始设计中两者间存在“观察者”关系。用户操作也不是由视图来捕获和处理。控制器呈现视图并向其显式传递显示数据。
在 ASP.NET 中,当提及 MVC 时,应该指定所说的是 Model2 还是手动实现的 MVC,本专栏开始曾简要介绍过此内容。(如果希望了解更多信息,请阅读《MSDN 杂志》上的 文章 ASP.NET MVC Framework

ASP.NET MVC Framework 和手动 MVC
为了使每个页面(或一组页面)都拥有一个控制器类,使用 ASP.NET MVC Framework 和手动实现 MVC 模式这两种方法有什么区别吗?在 SoC 方面,我没有发现任何显著差别。在以上两种情况下,您都有一个完全独立于视图和代表业务层或服务层网关的模型的控制器类。
在可测试性方面,Model2 模式要优于 MVC 模式的原始设计,因为前者出色地强制分开了视图和模型,且视图非常精简,仅这一个方面就可以有效地测试为 ASP.NET MVC Framework 编写的代码。此外,在 Model2 中有一种可在视图和控制器之间建立的不严格的约定。在 ASP.NET MVC Framework 中,此约定由 ViewData 容器对象或视图类的 ViewPage<T> 基类表示,其中后者表示效果更好。
与 ASP.NET Web 窗体相比,Model2 实现执行请求操作的速度更快。在 Model2 实现中,您只需要一个用来处理 HTTP 请求和调用控制器的组件,无需动态创建页面类;查找控制器依据的算法与 Web 窗体方案中查找 HTTP 处理程序相比简单得多;同样,您也不需要使用页面生命周期、视图状态或服务器控件。在 Model2 实现中,如 ASP.NET MVC Framework,您会拥有一个更灵活的运行时环境。接下来我们将进一步探讨此内容。
在通过 Web 窗体编程模型实施的 MVC 方案中,所有用户操作均源于 HTTP 发布。Web 服务器捕获这些请求,然后将其映射到 Page 类。Page 类历经其整个生命周期,如图 1 所示。然后,Page 类找到正确的控制器并调用一种方法。执行此方法会修改模型。其余的页面生命周期涉及到刷新视图。
在使用 ASP.NET MVC Framework 实施的 Model2 方案中,生成下一个视图的过程更简单。URL 路由模块解析 URL,并根据 URL 确定要使用的控制器,然后在所选的控制器上调用操作,该控制器为新视图收集新数据并传递此信息。该视图收集随后作为对捕获请求的响应返回到浏览器的 HTML。此视图是被动主题,实际上不需要进行测试。但是,您需要对控制器进行重点测试。
Model2 是改善应用程序可测试性的优先选择。但是,如果您仍需要 Web 窗体运行时环境来支持会话、缓存、服务器控件甚至视图状态(随 MVC 模式一起提供),该怎么办?这是否意味着您不得不放弃 SoC 和可测试性?当然不是。您可以选择使用我在开始提到的 MVC 的另一变体 — MVP 模式。它适用于 Web 应用程序,且不需要临时运行时。

MVP 模式
MVC 的原始设计有一个重要缺陷:更新视图的机制。您在图 4 中看到的信息流显示视图将用户操作传送到控制器,而从模型接收更改通知。接下来,视图需要完全了解模型才能获取更新的数据以及进行刷新。MVP 是 MVC 的变体,能够更清晰地分离三层架构中的每个元素。图 6 显示了 MVP 模型中各部分之间的典型交互。
图 6 运行中的 MVP 模式(单击图像可查看大图)
在 MVP 中,视图将用户输入转发给表示器,然后从表示器接收新数据以进行更新。反过来,表示器通过与模型交互来处理请求。
在原始 MVC 中,没有任何明确约定规定视图需要哪些数据。在 MVC 中,视图保留对模型的引用并运行其自己的逻辑来选择所需数据,然后将其添加到 UI 元素。有了这个逻辑,视图不像其在进行测试时那样被动了。此外,视图在某种程度上依赖基础 UI 平台。
在 MVP 中,表示器实际上是最终用户和应用程序之间的中介。表示器可以呈现视图并与模型交互,因此,大部分表示逻辑位于表示器中。由于表示器只是一个普通类,不包含任何 UI,因此从本质上来说,它是一个可测试性更高的类。您可以在 2006 年 8 月出版的《MSDN 杂志》中的“设计模式”专栏中看到如何在 ASP.NET 中实际实施 MVP 模式。MVP 由 Microsoft Web 客户端软件工厂提供本机支持,后者是一个与 ASP.NET 相关的应用程序块的集合。MSDN 中提供了 MVP 快速入门
MVP 是一个通用的 UI 模式,与 Model2 不同之处在于它并不专用于 Web,但 Model2 (ASP.NET MVC Framework) 和 MVP 基本上是相同的。有趣的是,这两个模式最初都不是设计为通用模式。
从技术层面来讲,Model2 和 MVP 之间只有一点不同:控制器(在 MVP 中称为表示器)和视图之间的约定。Model2 中对约定的定义欠严谨,而 MVP 中的定义比较正规。
在 MVP 中,视图实现一个界面,表示器仅使用该界面上的成员与视图进行交互。因此,例如表示器使用界面中的方法和 getter 读取视图中的输入数据,并使用界面中的方法和/或 setter 设置视图的新值。在 Model2 中,则使用强类型化的容器。
通过使用 MVP,表示逻辑可以独立于 UI 平台存在,这样在不同的平台间重复使用相同的表示器会更容易。此外,相同的表示器可以处理同一应用程序中的不同的视图,因此可启用软件即服务 (SaaS) 方案。最后,利用 MVP,您还获得了一个组件(表示器),其中可能存储了一些逻辑可在页面间导航。是在内部实现逻辑还是使用 Windows Workflow Foundation (WF) 组件取决于您自己的意愿。

页面控制器模式
MVC、Model2 和 MVP 是 ASP.NET 运行时设计的外部模式。ASP.NET 的 Web 窗体编程模型基于另一种设计模式,即页面控制器模式。页面控制器模式要求创建核心对象(动态创建的从代码隐藏类继承的类),该核心对象可以处理所有指向特定网页的 HTTP 请求(请参见图 7)。
图 7 页面控制器结构
如果自定义此模式,您可能会因为在页面间重复使用逻辑(包括导航逻辑)而受益。只需通过创建 Page 类的层次结构然后将导航逻辑和表示逻辑合并到层次结构较高的类中,以使该逻辑可供派生类使用,即可自定义该模式。自定义页面控制器是一个有效的方式,可以提高表示逻辑和导航逻辑的可重用性,而无需切换到一种全新的模式。
应用 MVP 实际上意味着通过实现 Page 类上的接口并使用一个单独的组件(表示器)处理用户操作,以其他方式来设计页面的代码隐藏类。不会对运行时环境进行任何更改,因为请求将以常规方式路由至代码隐藏类。
创建 ASP.NET MVC Framework 项目意味着对运行时环境进行更改以启用前端控制器(URL 路由模块),进而根据控制器上的操作处理请求。这两个选项都会对应用程序的开发产生影响。
第一个要确定的问题是是否需要视图状态和服务器控件。在某些情况下,您可能不想使用这些控件。例如,如果没有服务器控件,使用 CSS 对 UI 进行样式设计会更简单。在当前情况下,使用包含服务器控件的 AJAX 比使用 ASP.NET MVC Framework 方案更简单。但如果没有丰富的服务器控件,很难构建包含大量数据输入或基于表的复杂视图的应用程序。因此,主要是找到一个平衡点。关键问题是您是否需要服务器控件和视图状态。这个问题没有明确的答案。
如果您决定最好还是坚持使用 Web 窗体,但仍想偶尔使用 SoC,则可以选择使用 MVP。MVP 是一个非常重要的 UI 模式,在相对简单的应用程序中实施该模式可能有些浪费。另一方面,在企业级应用程序使用 MVP 效果非常好,因为此时您真正需要在多个平台之间和 SaaS 方案中重复使用尽可能多的表示逻辑。