你的位置:首页 > ASP.net教程

[ASP.net教程]使用Micrisoft.net设计方案 第三章Web表示模式


第三章Web表示模式

体系结构设计者在设计第一个作品时比较精简和干练。在第一次设计时,并清除自己做什么,因此比较小心谨慎。第二个作品是最危险的一个作品,此时他会对第一个作品做修饰和润色,以及把第一次设计的边缘性设计思想都用在第二个作品,结果导致设计过头。

最初的Web很简单,只是有几个简单的Html页面组成,实现信息共享。随着业务的发展,需要根据业务来决定显示什么,于是开发了CGI编程,把大量的业务逻辑写到CGI中,然后输出到页面。随着发展,CGI编程模式受到了挑战,不能满足发展的需求,于是开发了Asp/JSP等叶面编程模型,这样可能把业务逻辑通过脚本嵌入到Html页面中,这样大量重复的代码充斥在系统中,导致难于维护。

在设计Web系统时有一个矛盾,那就是简单和复杂的冲突。我们在设计系统中力求简单和干练。但为了实现重用以及随着业务复杂性的增加,会不可避免的应用复杂设计方式。如何平衡这个二者呢?有一个办法可以帮组我们衡量,那就是这种引入的复杂性是为了满足当前需求呢,还是为了满足将来需求呢?如果是前者那就可以增加复杂性,这种是合理的。如果是为了将来预留的,那就是设计过头。因此在设计时,要了解关键Web程序设计问题、可能的解决方案以及利弊。

模式概述

此模式集群直接从长期存在的MVC开始,自从业务逻辑和表示逻辑分开后,该设计模式经久考验。该模式最初是在设计阶段编写,最后被微软映射到了Asp.NET MVC平台上。

 

 

在用 Microsoft® ASP.NET 实现 MVC 时,是从一个简单系统示例作为起点的:编写在一个页面上,而且将应用程序逻辑嵌入表示元素中。随着系统越来越复杂,使用了 ASP.NET 的代码隐藏功能来将表示代码(视图)与模型控制器代码分开。这始终能够很好地工作,直至要求迫使您不得不考虑在没有控制器的情况下重复使用模型代码以避免 应用程序冗余。此时,需要创建独立模型以抽象化业务逻辑,并使用代码隐藏功能使模型适应视图代码。然后,实现过程结束对该 MVC 方法测试含义的讨论。

迄今为止,使用 Model-View-Controller 模式时一直强调模型和视图;控制器扮演相对次要的角色。实际上,在此模式中工作的控制器是 ASP.NET 中的隐式控制器。它负责感知用户事件(请求和回发),并将这些事件写入适当的系统响应。

在 基于 Web 的动态应用程序中,在每次进行页请求时都会重复许多共同任务,如用户验证、确认、提取请求参数和查找与表示相关的数据库。如果不对这些任务进行管理,则会很快导致不必要的代码重复。因为这些任务与感知用户事件和确定正确的系统响应完全相关,所以用来放置该行为的逻辑位置是在控制器中。

功能更强大的控制

此群集中的下一个模式是 Page Controller (页面控制器),该模式是对 Model-View-Controller 的优化,并且能够满足下一个级别的复杂程度。此模式在页面范围中使用一个控制器,接受来自页请求的输入,针对模型调用请求的操作,然后确定要用作结果页面的正确视图。重复逻辑(如确认)被移到了基本控制器类中。

使用 ASP.NET 实现 Page Controller 用常见的外观示例阐述了 ASP.NET 内置的页面控制器的强大功能。实现过程还结合使用 Template Method(模板方法)模式与 Page Controller(页面控制器)模式来定义操作中的算法框架,并将其中的一些步骤交由子类负责

随 着应用程序越来越复杂,页面控制器最终在基类中累积了大量逻辑,此问题通常通过加深页面控制器的继承层次结构来解决。如果应用程序十分复杂,这两种因素都 会导致代码难以维护和扩展。同样,某些应用程序需要动态配置导航图,这有可能跨越多个页面控制器。达到这种复杂程度时,应该考虑 Front Controller(前端控制器)。

Front Controller , 是该目录中的下一个模式,它也是对 Model-View-Controller 的优化。在前端控制器中,所有请求都通过单个(通常是两部件)控制器来传送。控制器的第一个部件是处理程序,第二个部件是 Commands(命 令)的层次。命令本身是控制器的一部分,代表控制器触发的特定操作。在执行该操作之后,命令选择要使用哪个视图来呈现页面。通常,构建的此控制器框架使用配置 文件将请求映射到操作,因此,它在构建之后便于更改。当然,其缺点在于这种设计固有的复杂程度。

筛选器和缓存

此群集中的最后两种模式涉及到筛选器和缓存。

I ntercepting Filter (截取筛选器)为以下问题提供了解决方案:如何针对 HTTP 请求实现常见的预处理和后处理。在 Intercepting Filter 中,最适合执行非应用程序特定的常见任务,如安全性检查、日志记录、压缩、编码和解码。 Intercepting Filter 通常涉及到执行某个特定任务。在针对 HTTP 请求执行多项任务时,多个筛选器会链接到一起。在 ASP.NET 中使用 HTTP 模块实现 Intercepting Filter 强调可在 ASP.NET 中方便地实现此模式。

Page Cache (页面缓存)通过保留创建成本极高的常用动态网页的副本来满足 Web 应用程序日益增加的可伸缩性和性能要求。在最初创建该页面之后,会发送一份副本以便响应以后的请求。Page Cache 还讨论几个关键的缓存设计因素,如缓存刷新、数据刷新和缓存粒度。在 ASP.NET 中使用绝对过期实现 Page Cache 阐述了 ASP.NET 内置的页面缓存功能。

PageController(页面控制器)

上下文

您已经决定使用 Model-View-Controller (MVC) 模式来将动态 Web 应用程序的用户界面组件与业务逻辑分隔开来。要构建的应用程序将以动态方式构造网页,但网页间导航多为静态导航。

问题

如何以最佳方式为适度复杂的 Web 应用程序构建控制器,从而既能避免代码重复,又能实现重用性和灵活性?

影响因素

以下因素影响这种情况中的系统,在考虑上述问题解决方案时必须协调这些因素:

MVC 模式通常主要关注模型与视图之间的分隔,而对于控制器的关注较少。在许多胖客户端方案中,控制器和视图之间的分隔相对次要,因此通常会被忽略 [Fowler03]。但在瘦客户端应用程序中,视图和控制器本来就是分隔的,这是因为显示是在客户端浏览器中进行的,而控制器是服务器端应用程序的一部 分。因此有必要对控制器进行更为仔细的研究。

在动态 Web 应用程序中,多用户操作可以导致不同的控制器逻辑,然后显示相同页面。例如,在基于 Web 的简单电子邮件应用程序中,发送邮件和从收件箱中删除邮件这两个操作都可能将用户返回(刷新后的)收件箱页面。虽然这两种活动完成之后显示相同页面,但应 用程序必须根据上一页面以及用户所单击的按钮来执行不同的操作。

显示大多数动态网页的代码都包括非常相似的步骤:验证用户身份、从查询字符串或表单域中提取页面参数、收集会话信息、从数据源检索数据、生成页面动态部分以及添加适用的页眉和页脚。这可能导致大量的代码重复。

脚 本化服务器页面(例如 ASP.NET)可能很容易创建,但在应用程序不断增大时可能带来一些缺点。脚本化页面不能较好地分隔控制器和视图,因而降低了重用的可能性。例如,如果 多个操作将导致相同页面,在多个控制器之间重用显示代码则会比较困难,这是因为显示代码与控制器代码混合在一起。对散布于业务逻辑和显示逻辑之间的脚本化 服务器页面也更加难以进行测试和调试。最后,开发脚本化服务器页面要求同时精通开发业务逻辑和制作美观高效的 HTML 页面,而很少有人兼备这两项技能。基于上述考虑,因此有必要最大程度地减少脚本化服务器页面代码,而在实际类中开发业务逻辑。

正如 MVC 模式中的相关叙述,测试用户界面代码往往耗时而单调。如果可以分隔用户界面专用代码和实际业务逻辑,测试业务逻辑则会更为简单,且可重复性更强。对于显示部分和应用程序控制器部分都是如此。

通用外观和导航结构往往可以提高 Web 应用程序的可用性和品牌认知度。但通用外观可能会导致显示代码重复,特别是在脚本化服务器页面中嵌入代码时。因此,需要一种机制以提高页面间显示逻辑的重用性。

解决方案

使用 Page Controller 模式接受来自页面请求的输入、调用请求对模型执行的操作以及确定应用于结果页面的正确视图。分隔调度逻辑和所有视图相关代码。如果合适,创建用于所有页面控制器的公用基类,以避免代码重复并提高一致性和可测试性。图 1 显示了页面控制器与模型和视图的关系。

 

页面控制器可接收页面请求、提取所有相关数据、调用对模型的所有更新以及向视图转发请求。而视图又将根据该模型检索要显示的数据。定义独立页面控制 器将分隔模型与 Web 请求细节(例如会话管理,或使用查询字符串或隐藏表单域向页面传递参数)。按照这种基本形式,为 Web 应用程序中的每个链接创建控制器。控制器因而将变得非常简单,因为每次仅须考虑一个操作。

为每个网页(或操作)创建独立控制器可能会导致大量代码重复。因此应该创建 BaseController 类以合并验证参数(请参阅图 2)等公用函数。每个独立页面控制器都可以从 BaseController 继承此公用功能。除了从公用基类继承之外,还可以定义一组帮助器类,控制器可以调用这些类来执行公用功能。

 

如果多数页面相似,并且可以将公用功能放入一个基类,则此方法非常有效。页面变化越多,必须插入继承树的级别也就越多。比如,所有页面都分析参数,但只有显示列表的页面才从数据库检索数据,而需要输入数据的页面则会更新模型而不检索数据。现在可以引入两个新基类,即 ListController 和 DataEntryController,这两个类都是继承 BaseController 而得到的。然后列表页可以从 ListController 继承,而数据输入页则可以从 DataEntryController 继 承。虽然这种方法在这个简单示例中非常有效,但在处理实际业务应用时,继承树可能很深且非常复杂。您可能希望向基类中添加条件逻辑,以适应某些变体,但如 此操作将违反封装原则,基类也会因此在更改系统时造成较大麻烦。因此在应用程序变得更为复杂时,应当考虑使用帮助器类或者 Front Controller 模式。

因为很多时候都需要对 Web 应用程序使用页面控制器,因此多数 Web 应用程序框架都默认实现页面控制器。大多数框架以服务器页面的形式包含了页面控制器(例如 ASP、JSP 和 PHP)。服务器页面实际上组合了视图和控制器的功能,但没有提供显示代码与控制器代码之间的相应分隔。遗憾的是,对于有些框架,混合视图相关代码与控制 器相关代码很轻松,但要正确分隔控制器逻辑却很困难。因此,Page Controller 方式在很多开发人员中口碑不佳。现在很多开发人员都将 Page Controller 与较差设计联系在一起,而将 Front Controller 与较好设计联系在一起。实际上,这种感觉是由于具体的实现在不完善的情况下造成的;Page Controller 和 Front Controller 都是可行性极佳的体系结构选择。

因此,最好将控制器逻辑单独放入可以从服务器页面调用的独立类。ASP.NET 页面框架提供了可以实现这种分隔的完善机制,这种机制称为"代码隐藏类"。

变体

大多数情况下,页面控制器取决于基于 HTTP 的 Web 请求的具体细节。因此,页面控制器代码通常包含对 HTTP 头、查询字符串、表单域、多部分表单请求等的引用。因此在 Web 应用程序框架之外测试控制器代码非常困难。唯一方法是通过模拟 HTTP 请求和分析结果来测试控制器。这种类型的测试既费时且易出错。因此,要提高可测试性,可以将依赖 Web 的代码和不依赖 Web 的代码分别放入两个单独类中(请参阅图 3)。

 

在此示例中,AspNetController 封装了在应用程序框架 (ASP.NET) 上的所有依赖项。例如,它可以提取来自 Web 请求的所有传入参数,并使用独立于 Web 界面的方式(例如,使用集合)将其传递至 BaseController。此方法不仅可提高可测试性,而且允许通过其他用户界面重用该控制器代码,例如胖客户端界面或自定义脚本语言。

此方法的缺点在于增加了开销。现在新增了一个类,并且在处理每个请求前必须首先对其进行转换。因此,应尽可能控制器受环境影响的部分,并权衡选择降低依赖性与提高开发效率及执行效率。

结果上下文

使用 Page Controller 模式存在下列优缺点。

优点

简单性。由于每个动态网页由特定控制器处理,所以这些控制器仅需进行有限范围的处理,从而可以保持简单性。由于每个页面控制器仅处理一个网页,所以 Page Controller 尤其适用于导航结构简单的 Web 应用程序。

内置框架功能。在 其多数基本形式下,控制器已经置入大多数 Web 应用程序平台。例如,如果用户单击网页中指向由 ASP.NET 脚本生成的动态页面的链接,则 Web 服务器将分析与该链接关联的 URL,并执行相关 ASP.NET 页面。实际上,这些 ASP.NET 页面是用户所执行操作的控制器。ASP.NET 页面框架还提供了用于执行控制器代码的代码隐藏类。代码隐藏类提供了控制器和视图之间的更好分隔,并且允许创建合并所有控制器公用的功能控制器基类。有关 示例,请参阅"在 ASP.NET 中实现 Page Controller"。

增强型重用性。创建控制器基类可以减少代码重复,并允许在不同的页面控制器重用公用代码。可以通过在基类中实现重复逻辑来重用代码。然后,所有具体的 Page Controller 对象将自动继承此逻辑。如果该逻辑的实现对于不同页面而有所不同,则仍可以在基类中使用 Template Method,并实现基本的执行结构;具体子步骤的实现可能因页面的不同而有所变化。

可扩展性。通过使用帮助器类,可以很简便地扩展页面控制器。如果控制器中的逻辑过于复杂,则可以向帮助器类委派部分逻辑。除了继承之外,帮助器类还提供了另一种重用机制。

开发人员责任的分隔。使用 Page Controller 类有助于分离开发队伍中各成员的责任。控制器开发人员必须熟悉由应用程序所实现的域模型和业务逻辑。另一方面,视图设计者可以专注于结果的显示风格。

缺点

由于其简单性,Page Controller 是大多数动态 Web 应用程序的默认实现方式。但是应该了解下列限制:

每个页面一个控制器。 Page Controller 的 主要缺点是要为每个网页创建一个控制器。该特点非常适用于具有一组静态页面和简单导航路径的应用程序。有些较为复杂的应用程序要求对页面和页面间的导航映 射进行动态映射。如果将此逻辑分散于众多页面控制器,则可能导致应用程序难以维护,即使某些逻辑可以放入基本控制器。另外,Web 框架的内置功能可能会降低对 URL 和资源路径命名时的灵活程度(虽然可以使用 ISAPI 筛选器等较低级别的机制进行一定程度的补偿)。在这些方案中,请考虑使用 Front Controller 截取所有 Web 请求,并根据可配置规则将这些请求转发至相应处理程序。

较深的继承树。继承似乎是基于对象编程方式的既最可爱又最讨厌的功能之一。如果仅通过使用继承来重用公用功能,则可能降低继承层次结构的灵活性。有关详细信息,请参阅"在 ASP.NET 中实现 Page Controller"。

对于 Web 框架的依赖。在基本形式中,页面控制器仍然依赖于 Web 应用程序环境,且不能单独进行测试。可以使用包装机制来分隔依赖 Web 的部分,但这样需要增加一个级别的间接性。

测试考虑事项

因为Page Controller 依赖于 Web 应用程序框架的具体细节(例如,查询字符串和 HTTP 头),因此不能在 Web 框架之外对控制器类进行实例化和测试操作。如果需要对控制器类运行一组自动单元测试,则每次测试时都必须启动 Web 应用程序。然后必须使用可执行所需功能的格式提交 HTTP 请求。此配置为测试带来许多依赖性和不良影响。要提高可测试性,请考虑将业务逻辑(包括变得更加复杂的控制器逻辑)与依赖 Web 的代码分隔开来。