在 ASP.NET 框架中使用 UML 进行模型-视图-控制器 (MVC) 架构的健壮性分析






4.94/5 (153投票s)
本文将通过 UML 在 ASP.NET 中的应用,增强您对模型-视图-控制器 (MVC) 结合的健壮性分析的认识。本文是我关于 ASP.NET 架构与设计的系列文章的续篇。
引言
最近,我写了一篇关于 ASP.NET 中主页框架开发的文章。主页框架是 ASP.NET UI.Page
框架的扩展,大部分与页面的静态外观有关。然后,我开始收到关于页面动态行为、控制页面用户控件之间的交互以及不同页面之间通信的电子邮件。因此,我认为有必要讨论 .NET 框架的这一方面,而这离不开嵌入在 ASP.NET 框架各处的模型-视图-控制器设计模式。本文分为两部分。第一部分,我们将结合 UML 详细讨论 ASP.NET 中的模型-视图-控制器设计模式,并以一个示例应用程序的实现作为奖励来结束我们的讨论。在下一部分,我们将通过解释不同类型的细粒度控制器及其设计和实现来总结页面的动态方面,并将讨论 HTTP 管道的细节以及最佳使用方法;敬请期待,精彩内容即将呈现 : )。
模型-视图-控制器设计模式
这是所有设计模式中最经典、最著名的模式,尤其是在讨论用户界面时。用户界面本质上是某种模型数据的表示层。如果数据发生变化,视图会相应更新,反之亦然。在这种特定场景下,仔细观察会发现,有两个对象参与执行此任务,即 **视图 (View)** 和 **模型 (Model)**。数据模型可能有一个或多个视图。例如,一个视图可以是网格表示,另一个可以是某种图形图,如条形图或饼图等。其外观如下所示:
我知道,您现在应该有这些问题了,比如视图对象如何与模型对象通信以及反之亦然,我们希望如何看待模型,模型的部分将去哪个视图,以及视图的部分如何。现在,我们谈论另一个对象,控制器对象;这个控制器对象充当模型和视图之间的粘合剂。控制器对象基本上是一个业务对象,它提供了我们想要如何查看数据、应该应用哪些规则来呈现数据等答案,以及如果有人更改视图中的某些参数会发生什么,如果这是一个交互式应用程序,如何响应某些键盘或鼠标事件,有时您需要更新模型并向其他视图发送数据已更改的消息,以便它们可以回发请求以更新其自身内容。您很快就会得到所有这些答案!
健壮性分析
模型-视图-控制器可以通过健壮性分析进行最佳的视觉描述,该分析最初由 Ivar Jacobson 在他获奖的《面向对象软件工程》一书中提出(见下文参考),并由 Doug Rosenberg 等人在其著作《使用 UML 进行用例驱动的对象建模》中进一步阐述。健壮性分析涉及分析用例的叙述文本,识别将参与这些用例的第一个粗略对象集,并根据它们扮演的角色对这些对象进行分类,它有助于您在模型-视图-控制器范式中划分对象。健壮性分析能够持续发现对象,并帮助您确保在开始任何其他设计或开发之前已识别出大多数主要领域类。Ivar 将它们分类为:
- «实体»对象代表长期存在的对象,主要处理持久化状态。
- «边界»对象表示系统与环境之间的通信链接。
- «控制»对象代表特定于用例的行为。
实体对象 (Entity objects) 不仅仅是您的边界对象正在查找的信息或数据。这些可能是数据库表、Excel 文件,也可能是“瞬态”会话或缓存数据等。
边界对象 (Boundary objects) 是参与者(例如用户)将在您的软件系统中与之通信的对象。这些可能是您系统中的任何窗口、屏幕、对话框和菜单,或其他 UI。在分析用例时,您可以轻松识别它们。
控制对象 (Control objects) (控制器) 是业务对象或您的业务 Web 服务。在这里,您可以捕获用于筛选要呈现给用户请求的数据的业务规则。因此,控制器实际上控制着业务需求或业务本身。
这里是这三个 UML 样式的视觉图标
游戏规则
当您在分析用例和制作对象之间的交互图时,应强制执行以下四个基本规则:
- 参与者只能与边界对象通信。
- 边界对象只能与控制器和参与者通信。
- 实体对象只能与控制器通信。
- 控制器可以与边界对象和实体对象以及其他控制器通信,但不能与参与者通信。
如果您看上面的图,您可以看到参与者访问任何对象的唯一入口点是通过边界对象。边界对象不能相互通信。而控制对象可以自由地与所有对象通信,除了它无法访问参与者或系统用户。实体对象也不能相互通信;它们可以通过控制对象访问彼此。此外,实体对象只能通过控制对象访问边界对象。因此,控制对象确实是各层之间的通信媒介,这就是为什么它适用于业务规则。
健壮性分析与模型-视图-控制器
现在,我们有了所有规则,让我们看看这些对象与模型-视图-控制器范式是如何关联的。好消息是,它们与从该分析派生的对象有一对一的映射关系。
- 实体对象映射到模型对象,
- 边界对象映射到视图对象,以及
- 控制器在两者中是相同的。
另一个好消息是,这些相同的规则也适用于这些对象。这意味着,当我们进行健壮性分析时,我们可以使用模型-视图-控制器对象来代替实体-边界-控制器对象。我们的对象现在看起来是这样的:
这是 MVC 的一个简单而假设的顺序图。您在此图中所看到的是,一个 Web 用户发起了一个查询,并生成了一个由控制器处理的事件,控制器获取了模型所需的信息,验证了信息,然后将结果集传回视图。
模型-视图-控制器是一种分离不同层的方法,这样对于中等到非常复杂的应用程序来说,它会更容易维护。当您为 Web 开发某些内容时,它将是最适合的,因为业务规则会随着时间频繁变化。此外,它是编写面向服务架构应用程序的最佳方式,在这种架构中,您将业务规则编写/封装在一个独立的 Web 服务中,而不是数据访问层,而服务仅处理与模型/数据相关的内容。由于用户界面也已分离,您可以构建强大的自定义控件,并将它们放在用户控件中作为页面片段进行部署。最后,剩下的就是,在设计任何解决方案时,您只需要构建适当的交互,强制执行上述规则。
示例
那么,让我们看看我们现在所处的位置:我们现在知道这些模型-视图-控制器对象是什么,它们看起来像什么,以及当我们分析特定的用例场景并从中创建交互图时应该遵循哪些规则。让我们举一个示例场景,并使用此技术进行解释!
用例 (UC.1.001)
用户应能够登录 Web 应用程序。
这是此用例的 UML 图,它显示了参与者(Web 用户)与登录用例之间的关系,在进入分析阶段时,它将更加详细。我选择了一个非常简单的登录案例,并将注册用例留给我们的示例,以便一开始就能简单易懂。
在进行任何分析之前,最好,或者我称之为必须,您应该有一个可视化的原型。我们的原型看起来像这样:
这意味着,用户会看到一个登录屏幕,他可以在其中输入身份信息并按“登录”按钮以访问系统。用户还可以选中复选框以记住他;在这种情况下,需要在他的系统上启用一个 cookie。此外,当他登录时,会在服务器端创建一个新的会话,以在他使用 Web 应用程序时保持其活动状态。有了所有这些,让我们使用健壮性分析图来详细说明这个场景:
让我们仔细看看上面的图。用户与登录屏幕(视图)交互,该屏幕有一个用于登录的提交按钮(UML 中的“has-a”关系由一端带有菱形线的线表示),以及一个用于启用 cookie 存储的复选框。当用户单击这两个按钮中的任何一个时,请求将发送到 MainController
对象,然后该对象决定如何处理这些操作,因为它拥有处理这些事件的所有逻辑,然后从安全控制对象进行一些验证,该对象又从数据访问对象获取数据。MainController
然后为此用户创建一个会话,它还将用户的信息(如加密密码)存储在 cookie 存储或其他地方。通过这个分析,我们现在非常清楚,除了登录屏幕之外,此场景还将涉及四个其他对象;MainController
、SessionState
对象、CookieStore
对象和数据库访问对象。它确实给了您关于完成特定工作所需对象的一些想法,但它并没有告诉您完成这项任务所需的功能。为此,我们需要一个顺序图,这里是顺序图应该的样子:
仔细研究上面的顺序图后,我们可以安全地对我们的对象进行分类,应该有一个 MainController
,负责页面业务逻辑,以及一个 BusinessService
对象,负责所有验证、会话管理和 cookie 设置等。将有一个 DataAccessGateWay
(模型) 对象,它封装了与后端数据库通信的所有详细信息。这是类图的样子:
所有这些健壮性分析最终为我们提供了一个设计,使我们能够定义第一组详细的类图,这难道不是一件很美妙的事情吗?我知道您会喜欢它。
以上所有对象中最重要的就是业务对象,也就是控制器对象。现在,让我们仔细看看 ASP.NET 框架如何帮助我们定义这些控制器类。
ASP.NET 和模型-视图-控制器 (MVC)
MVC 的基本思想是将表示层与业务层和数据访问层(或模型)分开。让我们看看 ASP.NET 中是如何实现的。
让我们仔细看看这张图,Controller
绑定到某个模型,并在数据更改时更新 View
,反之亦然。因此,视图被动地依赖于模型。我们通过 ASP.NET 的代码隐藏功能来实现这一目标,页面/用户控件的逻辑与其通过“*.aspx”文件或“*.ascx”文件呈现的视图是分开的。而您添加到页面控制器(即 System.UI.Page
)的控件,您会尽量使其数据绑定,以保持一致性和同步性。此外,您可以使用代码隐藏功能在页面控制器中嵌入事件处理程序的逻辑。如果您的架构是面向服务架构,那么您可以引入另一个控制器作为业务服务(可能是安全服务),该服务根据需要与后端模型通信,并将安全数据返回给页面控制器。它看起来会是这样的:
到了展示的时候了 (奖励++)
作为奖励,我们将开发一个功能齐全的主从详细信息 Web 应用程序,该应用程序将为您提供有关模型-视图-控制器 (MVC) 及其内部工作原理的足够详细信息,以及不同用户控件之间通信的方式,而无需相互了解。此外,您还将获得一个由 OleDataLinkAdapter
和 MsSqlDataLinkAdapter
类组成的重用库。这些对象是 ADO.NET 连接和命令对象的包装器。
主从详细信息 ASP.NET 应用程序
该应用程序是一个 Web 应用程序,包含一个主页,由公司徽标、横幅、主内容和页脚组成。主内容应以网格视图的形式显示父子关系。将有两个网格,上面的网格是主网格,下面的网格是详细信息网格。当用户单击主视图中的任何项目时,详细信息/子视图将相应更改,并应显示相关的子数据。
用例 001:用户应能够查看/操作 Northwind 客户/订单表的详细信息。
我们将采用我上一篇文章中相同的技术,即面向模式的架构和设计 (POAD)。为此,我们需要我们计划设计的系统的原型模式。这是原型的样子:
所以,我们所见即所得,也就是说,我们的应用程序将由一个 MasterPage
、一个 HTML 框架容器、然后是一个 HTML 表组成,该表将进一步包含两个控件:Master-User-Control 和 Details-User-Control。这些用户控件反过来将包含 MasterDataGrid
和 DetailsDataGrid
视图。这是完成这项工作所需的对象的静态清单:
用例 001 的健壮性分析
现在我们对完成这项工作所需的对象有了一些了解,但在进行详细设计之前,我们需要分析用例,找出它们之间的正确交互和关系。这将使用健壮性分析来完成,同时牢记 MVC 概念。让我们为这个场景绘制概念健壮性图:
这意味着,用户会看到一个主从详细信息视图。上面的主网格视图用于选择主表项。因此,每当用户选择主项时,子视图都会相应更新。让我们看看如何实现对象之间的这些交互。仔细查看上面的图,您会发现,当用户从主网格视图中选择一个项目时,MasterUserControl
将收到通知,然后该通知会将此事件通知给监听器,即 MainController
对象。MainController
对象将此事件通知给另一个监听器,即 DetailsUserControl
。
这些用户控件可以直接访问模型(DataAccessGateway
),该模型使用 MsSqlDataLinkAdapter
来建立 MS-SQL 数据库连接(对于 OLEDB 连接,请使用另一个适配器 OleDataLinkAdapter
)。这是 MVC 及其内部工作原理的一个经典示例。这是此特定场景的对象交互图:
MasterController
和 DetailsController
是差异化控制器,它们负责一次控制一个用户控件并对其进行微调,而 MainController
对象充当这些控制器之间信息来回传递的集成器或通道。让我们看看我们计划它们如何工作。一个对象要成为通知对象,需要实现 INotifier
接口,而监听器对象需要实现 IListner
接口。此外,监听器对象需要将其自身附加到相关的通知程序才能被通知。在我们的例子中,我们有 Master-Controller,而 Details-Controller 是监听器。MainController
既是监听器又是通知程序,充当它们之间的粘合剂。
现在,此时,我们拥有关于负责工作的对象、它们的样子以及它们的行为和功能的所有信息。现在让我们设计它们并开发完整的类设计。这是此应用程序的详细类图:
使用 C# 和 ASP.NET 进行实现
代码层次结构如下图所示,您会在 Shams.Data.dll 中找到数据访问适配器,在 Shams.Web.UI.MasterPages.dll 中找到更新的主页框架库,并且我从 Web 的不同来源收集了一些库,您会在 Shams.Web.UI.WebControls.dll 下找到它们。示例应用程序位于 Shams.MVC.WebApp
命名空间中。您所需要做的就是打开 Shams.MVC.WebApp
解决方案,然后就可以开始了。并在 IIS 中创建的“Shams.MVC.WebApp.csproj.webinfo”文件中验证虚拟路径。这是它的样子:
<VisualStudioUNCWeb>
<Web URLPath = "https:///shams/MVC/WebApp/Shams.MVC.WebApp.csproj" />
</VisualStudioUNCWeb>
这里有一些来自应用程序的代码片段,从 web.config 开始。web.config 文件中的 AppSettings
部分将包含此应用程序的两个内容:NorthWind DataTable
的连接字符串,以及 MasterPageUserControl
的路径。
它看起来是这样的:
<appSettings>
<add key="MasterPageUserControl" value="MasterPageUserControl.ascx"/>
<add key="ConnectString001"
value="server=localhost;database=northwind;Integrated Security=true;"/>
<add key="ConnectString002"
value="Provider=SQLOLEDB;server=localhost;database=northwind;
Integrated Security=true;"/>
</appSettings>
模型是 DataAccessGateway
类,它返回用于主从详细信息数据网格控件的 DataSet
。它看起来是这样的:
它使用 MsSqlDataLinkAdapter
,它是 ADO.NET 连接和命令对象的包装器。这是一个单例类,所有函数都是静态的。这是该类的骨架:
在我们的特定场景中,DataAccessGateway
使用此类连接到 MS-SQL NorthWind 数据库,方法是调用它的 Connect(.)
函数。一旦获得此连接对象,您将调用另一个函数 FilldataSet(.)
来返回一个全新的数据集用于数据绑定。完成后,您将调用 Disconnect(.)
来关闭连接对象。
其他值得注意的类是控制器类,UcMasterDetails
(主控制器) 和另外两个类,UcMainContentsMaster
(主控制器) 和 UcMainContentsDetails
(详细信息控制器)。这是我们如何使用 UcMasterDetails
作为中介将这两个类连接起来的:
最后!这就是应用程序运行时应有的样子。 :)
我在应用程序中使用的 MVC 模式是最少的使用量,但它是所有页面之间和控件之间导航中使用的最佳模式。它还取决于您如何制定应用程序计划来创建原型应用程序。所以,各位,今天就到这里了,我试图用一些有用的实现应用程序来揭示 MVC 设计模式的所有内容。这是最常用且最常被误解的模式之一。我试图使其简单并为您提供它包含的所有详细信息,以及健壮性分析,这是分析此类应用程序的关键。
改进
任何系统都总有改进的空间,这里也是如此。我没有讨论任何效率方面的内容,因为这本身就需要一整篇文章。因此,我将这项责任推给您,让您阅读有关提高 ASP.NET 应用程序效率的文档,例如缓存、视图状态管理和会话管理,以及如何避免往返服务器,例如回发等。如果您能与我分享您的发现,我将不胜感激,非常感谢。
问答环节
以上是我的回答,但欢迎您将您的回答发送给我,地址是 shams@microteck.net,我将在 我的网站 上很快发布它们。
参考文献
- Ivar Jacobson 的《面向对象软件工程》。
- Erich Gamma 等人的《设计模式》。
- Martin Fowler 的《企业应用模式》。
- Rosenberg 等人的《使用 UML 进行用例驱动的对象建模》。
- UML 用例工具:来自 www.sparxsystems.com 的 Enterprise Architect。