65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 MVC II 设计模式进行 Web 应用程序开发,或者为什么遵循 MVC II 对我有帮助

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.18/5 (22投票s)

2004年12月5日

16分钟阅读

viewsIcon

150220

不损害ASP.NET机制的MVC基础设施。

摘要

本文将探讨创建企业级Web应用程序的各种方法。我将概述可能的架构,并解释为什么遵循MVC II、前端控制器(Front Controller)和应用程序控制器(Application Controller)(在本文中我统称为MVC)设计模式将为您带来最大的投资回报(ROI),包括应用程序维护和应用程序块的使用。为了使遵循MVC II的应用程序开发更加容易,开发人员可以采用现有的框架,这些框架将完成大部分MVC II的实现。这样的框架将使程序员只需处理应用程序逻辑和数据。我将以NWAF结束,并解释为什么使用它是您的应用程序MVC II基础设施的最佳选择。

问题

我见过的许多Web应用程序通常由一个包含页面可视化处理、应用程序逻辑以及从各种数据存储中获取或设置数据的Web页面组成(图A)。

图A

虽然这是开发小型应用程序最快的方法,但对于大型系统来说肯定不是这样,而且肯定无助于开发人员在时间尺度上维护这些应用程序。

编写处理所有应用程序方面的代码会导致几个问题

  1. 您为系统编写的代码组件很难被您或您组织中的其他人将来开发的应用程序使用。将应用程序逻辑代码或数据库访问代码分离到不同的类/程序集中不是更好吗?通过这样做,我们可以创建类库,以便将来在其他项目中受益。
  2. 例如,数据库的更改需要扫描所有页面并查找这些数据库更改发生的位置。只有到那时,我们才能遍历所有页面并修复我们的代码,使其与数据库更改兼容。如果数据库访问代码位于单独的类/程序集中,我们只需修改一次。
  3. 在开发大型应用程序时,我们需要编写的许多任务将从不同的页面检索。如果将业务逻辑和数据访问与应用程序页面分开,开发和维护它们将更容易。

这些原因(以及其他原因)促使我们中的一些人采用分层架构开发应用程序。分层架构非常简单易懂且易于采用。构建分层应用程序意味着您的应用程序将被划分为三个基本层:可视化层、业务逻辑层(BLL)和数据访问层(DAL)(图B)。

图B。

每一层都包含处理应用程序某个方面的类,并汇集在专用的程序集中。使用分层有助于开发人员知道在哪里可以找到应用程序的可视化代码、业务逻辑代码或数据访问代码。

每一层都构建在实现应用程序需求和面向对象实践的类之上,以最大限度地重用给定应用程序或其他现有和未来应用程序中的代码。

遵循分层架构的应用程序应该由仅处理可视化应用程序方面的页面构建。这些页面可能直接调用DAL类,或者通过BLL类间接调用(取决于应用程序设计),以处理应用程序逻辑并将数据检索回页面。可视化层类应该使用返回的DAL/BLL数据,以根据应用程序需求格式化该数据,并向最终用户显示格式化数据。

我在上面几行中描述的分层架构是Microsoft建议的系统创建范例。这种架构是否为创建易于维护的系统提供了最佳解决方案?这种架构最终是否产生了可重用的代码,可以轻松地移动并被其他应用程序使用?

让我们看看:仍然有几个方面使我们难以重用应用程序块和维护系统。最重要的一点是,页面与BLL/DAL代码之间仍然存在耦合,因为页面直接调用DAL或BLL类。页面与BLL/DAL类之间的最佳关系应该允许它们之间解耦。这样工作不会强制页面与DAL/BLL类之间的任何接触。因此,DAL/BLL设计者可以创建类而不必考虑哪个页面最终会调用它们。

解耦不仅意味着调用BLL/DAL类,还应该应用于从DAL/BLL类返回的数据。页面应该使用一个契约,该契约指定将从DAL/BLL类接收的数据和格式。页面不应该知道创建它应该工作的数据的类,也不知道这些类返回的结果。从它的角度来看,页面数据具有预定义的结构,位于某个特定位置,而页面的职责只是获取该数据,格式化它,并最终将其显示给用户。遵循这些规则,页面开发人员不依赖于DAL/BLL开发人员、BLL/DAL操作或返回的数据。事实上,以这种方式开发的面、DAL或BLL类可以用于任何系统(只要页面能在约定的位置找到预定义的数据结构)。

松耦合关系也应该适用于应用程序页面之间的关系。通常,页面重定向到其他页面涉及在源页面上编写代码以将用户重定向到其他页面,或使用HTML标签(如`<A>)。页面之间的硬编码连接会导致我们(开发人员和应用程序管理员)在需要更改应用程序流程时修改ASPX文件或代码隐藏文件。最佳解决方案是将应用程序流程与其页面分开,存储在单独的应用程序流程设置中。应用程序流程设置应包含所有应用程序流程规则,并应由应用程序使用以决定导航到哪个页面。应用程序流程设置应采用人类可读的格式(XML),以便人们无需编码即可更改应用程序流程。将页面彼此解耦并将应用程序流程移至专用的应用程序块有助于我们解决另一个与基于应用程序状态的页面导航相关的问题。在某些情况下,应用程序导航决策基于应用程序的当前状态,甚至基于对BLL/DAL类的调用返回的数据。在页面充当应用程序入口点(Page Controller设计模式)的分层工作中,将调用一个页面,一旦该页面调用其他层,在需要时可能会发生重定向到其他页面。如果应用程序逻辑可以在处理管道到达页面之前被调用,那么处理应用程序流程的一个点将更容易,同时创建仅处理格式化和显示数据的页面。这些页面可以在其他应用程序中使用,因为它们没有连接到任何其他系统类。

第三点更多地是关于企业开发,但我认为它适用于许多程序员/团队在多个不同项目上工作的任何环境。通常,在这种情况下,需要统一的开发结构,以便我们可以在整个企业中维护开发人员/开发团队之间的通用工作实践。统一的开发结构不应成为定义期望的开发架构的严格规则的专属集合。这些规则应通过使用实现这些规则的基础设施来强制执行。无论统一的开发结构如何,企业通常会为应用程序提供一组基础设施服务(例如,日志记录、审计、监控等)。提供这些服务的最佳方法是通过强制执行严格的应用程序流程管道,该管道将用于向应用程序交付企业服务。

当页面直接调用BLL/DAL类时,统一的开发结构和基础设施都将难以实现。

解决方案

显然,我不是第一个认识到这个问题并寻求更实用解决方案的人。这项工作是由“四人帮”完成的,其结果是著名的设计模式——MVC(Model-View-Controller)。MVC最初是为客户端应用程序设计的,然后被改编为Web应用程序。MVC的基本思想是将应用程序划分为视图(页面)、控制器(业务逻辑、应用程序流程层)和模型(DAL类和业务逻辑类)。实际上,ASP.NET是MVC的“纯粹”实现。但正如我所解释的,这种模型仍然是有限的,为了克服其不足,MVC模型II被建立起来。模型II明确定义了控制传入请求的控制器与负责可视化的页面之间的解耦。为了模拟MVC使用的事件,我们添加了命令设计模式来表示来自客户端的用户请求。正如我之前提到的,我们还将添加前端控制器,以提供应用程序的单一入口点。我们之前处理的应用程序流程将由应用程序控制器管理。因此,总而言之,前端控制器将拦截对应用程序进程的任何请求。前端控制器将提取指定特定用户请求的命令。该请求将由应用程序控制器管理,该控制器实际上调用模型类来在应用程序域数据模型上激活特定的应用程序策略。

现在,我们有了一个更接近我们目标模型。有一个中心点捕获传入的用户请求(前端控制器),并调用BLL类(应用程序控制器),这些类使用DAL和BLL类(模型)来返回模型数据。为了将系统逻辑和模型与系统页面分开,从DAL/BLL类返回的数据应遵循值对象(Value Object)设计模式。值对象充当由简单类型组成并仅包含类getter的类。基于返回的值对象,控制器决定调用哪个页面。页面仅使用在页面调用前步骤(值对象)中生成模型数据。

为了识别到达服务器的用户请求类型,我们将引入一个名为Action或Command的新概念(从现在开始我将使用Command,因为它是实际的命令设计模式)。Command是代表用户希望在服务器端处理的某个操作的字符串。前端控制器应解析传入请求中的命令,并通过预定义映射调用已映射到传入请求类型的特定命令。Command类包含对控制器的调用;这些调用是系统序列,更准确地说,是应用程序控制器(BLL类)。BLL类应返回VO(值对象),这些VO有助于Command决定应调用哪个页面。

Command的定义以XML定义形式存在,而不是预定义的代码。Command对象应在调用某个Command时在运行时动态创建(与ASPX页面工作方式相同)。通过将Command扩展到预定义的`指令`,允许用户通过返回模型数据(VO)来决定将调用哪个页面,从而创建能够处理应用程序流程的机制。这种流程机制节省了我们在页面中处理页面流程的需要,因此有助于我们创建可用于其他Web应用程序的松耦合页面。

您可能已经猜到,MVC II的一个缺点是需要大量的“管道”工作来实现它。换句话说,最好有一个工作基础设施,它已经实现了MVC + Command + Front Controller + Application Controller + Value object。这样的基础设施将使您能够只处理您的页面、控制器(BLL)和模型(DAL)类。

找到正确的MVC基础设施是一项艰巨的任务,因为ASP.NET引入了回调、服务器端控件、ViewState和其他您可能想要使用的服务。问题是,回发会阻碍MVC II的纯粹实现。Java实现使用Form action将命令传递到服务器端,但.NET由于回发,会控制Form action,不让我们使用它来传递命令。另一个问题是前端控制器的实现。如果您要坚持MVC II,前端控制器应该与视图分离。这意味着ASP.NET的默认页面控制器性质应该被改变。实现这种分离最常见的方法是创建一个专用的`IHttpHandler`作为前端控制器。但不幸的是,使用这种设计会损害页面默认服务,如回发、ViewState等。

最佳解决方案 - NWAF

ASP.NET中MVC的实现不多,而且没有一个能在不损害ASP.NET功能的情况下保留这些功能。实际上,有两种常见的MVC实现。一种是Microsoft UIP应用程序块,它是一个MVC实现,而不是MVC II(特别是在本文的上下文中)。此外,UIP是一个非常复杂的应用程序块。第二个选择是Java Maverick的.NET实现。Maverick要么实现了MVC II但不支持ASP.NET功能(上面提到的),要么实现了MVC(页面是页面控制器和视图)并完全支持ASP.NET功能。这两个MVC解决方案都提供了其他功能,如外部应用程序流程管理、状态管理等。

NWAF是唯一支持所有ASP.NET功能的基于MVC II的框架。除了作为纯MVC II实现之外,NWAF还被构建为一个通用框架,支持在与ASP.NET工作进程隔离的不同级别上运行您的控制器和模型代码。作为一个支持在其他进程中运行代码的框架,NWAF为运行在隔离进程中的对象提供服务。这些服务允许用户透明地创建和使用类,无论它们是运行在与ASP.NET相同的进程中还是在其他进程中。NWAF将Command扩展到XML声明级别,允许用户决定调用哪些控制器,并通过返回的值或从控制器抛出的异常来设置应用程序流程。在第一次调用服务器时,Command XML定义会在运行时编译成程序集,以最大限度地减少Command对应用程序性能的影响。除了其丰富的功能外,NWAF还采用了大量的可插入架构和堆栈拦截,实现了高度可定制的新用户定义服务、事件和其他任何需要干预NWAF默认流程的需求。

为了完全支持ASP.NET的特性,NWAF包含一个服务器端控件,允许用户为每个控件事件设置命令。除了为控件事件设置命令外,NWAF还支持通过隐藏的HTML Form字段或QueryString参数进行默认命令设置。

您可能已经猜到,NWAF是一个支持我之前提到的所有功能的框架。NWAF是根据我们研究的企业需求创建的,因此将成为企业用途的最佳解决方案。

为了更好地理解NWAF及其功能,我将简要介绍其架构。下一篇文章将更深入地探讨其架构和设计。

NWAF架构

为了解决回发问题,NWAF使用`IHttpModule`来拦截传入的请求。`IHttpModule`捕获所有传入的请求,无论其后缀是什么。因此,激活了后缀过滤,允许用户决定哪些后缀不参与NWAF处理。`IHttpModule`调用一个调度器,该调度器按以下顺序提取传入的命令:它提取导致回发的事件,以及HTML Form中是否存在默认命令,或者QueryString中是否存在默认命令。按照这个顺序找到的第一个命令将被NWAF处理。提取命令名称后,调度器会检查缓存中的命令对象。如果命令在缓存中找到,调度器将调用命令的`Do`方法(按照每个命令对象必须实现的`ICommand`接口声明)。如果命令未在缓存中找到,将调用`CommandFactory`通过命令声明创建新的命令对象。

在调用预定义的控制器之前,会进行命令调用,前提是命令XML定义中没有附加`EventObjects`。如果是这样,NWAF将从HTML Form集合初始化值对象,并将其传递给控制器。如果不是,则将Form集合和QueryString作为参数传递给调用的控制器。调用的控制器是通过使用堆栈拦截器实现的,因此可以为NWAF进程添加功能。命令调用`IController`接口中声明的控制器`Perform`方法。每个控制器都必须实现`IController`接口。

在调用“Controllers Perform方法”之后,将处理`Perform`方法中编写的用户代码。`Perform`方法的序列通常包括对域模型类的调用。域模型类返回`ValueObjects`作为结果,`ValueObjects`是表示模型数据的类。`ValueObjects`返回给命令。如果它在Commands定义文件中定义,Command会将`ValueObjects`作为参数传递给另一个控制器,否则会将`ValueObjects`存储在`HttpContext`中。然后,通过预定义的命令声明以及检查从控制器返回的值和异常,给定的命令可以

  • 重定向调用到另一个命令。
  • 传输/重定向执行到页面。
  • 继续执行默认的管道。

最终,通过默认管道或传输,页面将通过默认的ASP.NET机制执行。在页面的事件和页面控件的事件的预写代码中,`HttpContext`中的`ValueObjects`应与页面控件绑定。

如果NWAF配置文件中声明了高隔离,命令到控制器的调用顺序将有所不同。首先,命令将调用`IsolationController`,这是一个在COM+中注册为服务器应用程序的“虚拟”类(目前)。`IsolationController`检查是否存在持有所有对命令控制器的调用的`fa?ade`类。如果不存在,`IsolationController`将调用`IsolationFactory`来创建`Fa?ade`对象并存储它。`Fa?ade`将接收来自`IsolationController`的调用,并将它们重定向到请求的控制器。`Fa?ade`对控制器(通过使用堆栈拦截器实现)的调用使我们更容易地向NWAF添加功能。

控制器通过传递`EventObjects`来调用域模型类,结果是域模型类返回`ValueObjects`。为了使开发人员在低隔离和高隔离下具有相同的编程模型,NWAF中添加了两个类。第一个类为进入高隔离进程的每个请求提供上下文,并将其当前会话ID公开为上下文属性。第二个类为用户提供数据存储服务,并可能使用DataStorage来存储会话或应用程序数据。DataStorage是一个到数据存储的单例API。此外,它使得在ASP.NET进程内或进程外的数据存储对用户透明。

Web页面应继承自NWAF基类页面,该页面提供设置默认页面命令的属性。NWAF还提供了服务器端控件,允许用户以简单直观的方式为页面控件事件设置命令。`CommandControl`还负责渲染用户在回发时发送到服务器的设置的表示。

结论

NWAF是一个基于MVC II的Web应用程序框架的.NET实现。该框架能够加快Web应用程序的开发速度,并具有高度的代码可用性、应用程序维护能力,并增加了添加新服务的容量。

NWAF是一项开源计划,可以在这里找到。我们目前正在寻找有兴趣的专业人士加入我们,以完成这个项目。有关更多信息,请联系。

有关下载和运行NWAF的详细说明,请参阅这里

参考文献

© . All rights reserved.