烟台大学毕业论文(设计) 1. 综述 1.1 课题背景 1.1.1 B/S 系统的概述 B/S(Browser/Server)结构即浏览器和服务器结构。它是随着 Internet 技 术的兴起,对 C/S 结构的一种变化或者改进的结构。在这种结构下,用户工作界 面是通过 WWW 浏览器来实现,极少部分事务逻辑在前端(Browser)实现,但是 主要事务逻辑在服务器端(Server)实现,形成所谓三层 3-tier 结构。这样就 大大简化了客户端电脑载荷,减轻了系统维护与升级的成本和工作量,降低了用 户的总体成本(TCO)。 以目前的技术看,局域网建立 B/S 结构的网络应用,并通过 Internet/Intranet 模式下数据库应用,相对易于把握、成本也是较低的。它是 一次性到位的开发,能实现不同的人员,从不同的地点,以不同的接入方式(比 如 LAN,WAN,Internet/Intranet 等)访问和操作共同的数据库;它能有效地保护 数据平台和管理访问权限,服务器数据库也很安全。特别是在 JAVA 这样的跨平 台语言出现之后,B/S 架构管理软件更是方便、快捷、高效。 B/S 结构最大的优点就是可以在任何地方进行操作而不用安装任何专门的软 件。只要有一台能上网的电脑就能使用,客户端零维护。系统的扩展非常容易, 只要能上网,再由系统管理员分配一个用户名和密码,就可以使用了。 1.1.2 分层架构概述 在传统的系统设计中,将对数据库的访问、业务逻辑及可视元素等代码混杂 在一起。这样虽然直观,但是代码可读性差,耦合度高,也为日后的维护和重构 带来不便。为了解决这个问题,有人提出了 N 层架构思想,即将各个功能分开, 放在独立的层中,各层之间通过协作来完成整体功能。 多层架构的提出,是软件开发思想的一个重大进步。它的出现,在很大程度 上解决了软件开发中的强耦合问题,也为编写代码清晰、可维护性良好的系统提 供了思想基础。 Martin Fowler在《企业应用架构模式》一书中对分层架构的优势描述如下: z 开发人员可以只关注整个架构中的其中一层 z 可以很容易地用新的实现替代原有层次的实现 z 可以降低层与层之间的依赖 z 有利于标准化 z 有利于各层逻辑的复用 概括来说,分层架构设计可以达到如下目的:分散关注,松散耦合,逻辑复 用,标准定义。 当然,任何事物有利也有弊。分层架构的一大缺点就是降低了系统的性能, 因为本来直接完成的功能现在需要多次调用才能完成,自然使得性能下降。所以, 分层架构可以说是以牺牲系统性能换取可维护性的手段。 可以看出,系统的性能和可维护性是一对矛盾,鱼和熊掌和难兼得,所以在 使用分层架构设计系统时,要把握一个度,不能过于极端的强调性能或可维护性, 1 烟台大学毕业论文(设计) 而是应该根据系统的具体情况,取两者的折中。 目前,最成熟的分层架构体系应该是 Java 平台上的 J2EE 构架,目前,以 Struts、Hibernate和 Spring 为主的轻量级 J2EE 架构已经成为分层架构的经典。 而在其他平台,如.NET、PHP 平台,尚无成熟的分层架构框架。 1.1.3 设计模式概述 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类 编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易 被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真 正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。 GoF 的“设计模式”是第一次将设计模式提升到理论高度,并将之规范化, 本书提出了 23 种基本设计模式,自此,在可复用面向对象软件的发展过程中, 新的大量的设计模式不断出现。 1.2 课题提出 1.2.1 .NET 平台分层架构的现状及可研究性 微软(Microsoft)公司的.NET 平台自出现以来,已经经历了 1.0、1.1、2.0 及最新的 3.5 四个版本,而.NET 平台也凭借其先进的设计思想、丰富的类库、 强大的能力和完善的 IDE 及开发帮助文档获得了众多开发者的青睐。尤其在 Web 开发平台方面,ASP.NET 凭借其完善的面向对象模型和独树一帜的控件式开发方 式将 Web 开发这一技术领域提高到一个前所未有的新境界。 然而,令人遗憾的是,到目前为止,仍没有一个基于.NET 平台的经典分层架 构体系。反观其竞争对手 Java 平台,从 EJB 到轻量级框架,基于 J2EE 平台的分 层架构已相当成熟和完善。目前,基于.NET 平台的分层架构一般都是模仿微软 官方给出的分层范例——“.NET PetShop”。虽然“.NET PetShop”是一个经典 的基于.NET 平台的 B/S 系统分层架构示例,然而也有许多不足,如: 1.结构过于复杂,有点为分层而分层的感觉,对实际项目指导力不足。 2.实现方法单一。如数据访问层是使用的朴素实现,即手工组合参数,然后 动态生成 SQL 语句和调用存储过程。然而在实际中,可能有更多的实现方式,例 如通过 ORM 实现,这就需要进一步讨论数据访问层的共性,从更高的抽象层次上 对其进行理解和掌握,而不是仅仅把数据访问层看成一种具体的实现。 3.业务逻辑层没有针对接口编程,而是针对现实编程。这样,势必造成业务 逻辑层和表示层的强耦合。 4.表示层使用的是同步模型,没有异步模型元素。然而,在 Ajax 等异步模 型使用越来越广泛的今天,我们想知道如何将异步模型融入到分层架构中,而由 于异步模型的加入,各层之间会出现什么变化,.NET PetShop 并没有告诉我们 答案。 基于以上几点,可以看出,现在迫切需要形成一套理论,来指导.NET 平台上 的分层架构开发,而不是让其仅仅停留在模仿阶段。 2 烟台大学毕业论文(设计) 1.2.2 课题研究方向及目的 本课题的研究目的,是力图通过对分层架构思想、设计模式、软件工程、.NET 平台以及 Web2.0 思想等论题的研究,寻找一种合理、简练、通用、易用、安全、 具有良好的可维护性和可扩展性并且融入最新技术元素的基于.NET 平台的分层 架构模式。而且要通过一个完整的系统实例展现出来。 另外,设计模式也是本课题要讨论的话题之一。因为,做架构设计离不开设 计模式。但是,文章中不会专门讨论设计模式的话题,而是在用到的地方,进行 与分层架构模型有关的讨论,并研究其在与.NET 平台的结合中的特殊问题。 1.2.3 课题研究方法 本课题是一个兼具理论与应用的课题,所以,在研究过程中,两方面都要涉 及到。基于此,我对此课题采取的研究方法是:收集需求、提出方案、设计实现、 验证效果。 首先,应该从整体到部分,对整个分层架构体系各个需求进行收集,明确各 部件的职责;然后,针对其职责,提出几种设计方案,并进行设计实现;最后, 从耦合度、可扩展性、可维护性和性能等多方面对方案进行验证,提出对解决方 案的评价。 为了避免脱离实际,在研究本课题的过程中,将逐步完成一个实际的系统, 而所有的理论,都将直接作用在此系统上,贯穿于一个完整的系统开发过程中, 从而让理论在实践中得到检验。 本课题研究的 Demo 将是一个 BBS 系统,之所以选择 BBS 系统,有以下几点 原因: 1.规模适中。BBS 系统的规模适中,既能起到示例的作用,又不会因为太过 复杂而影响进度。 2.业务逻辑熟悉。一般来讲,在做某个系统时,业务逻辑的设计要和领域专 家合作,因为一般情况下软件开发人员对其他领域的业务流程并不熟悉。而 BBS 系统是我们常用的一种系统,大多数人对其业务逻辑非常熟悉。这样就可以免去 在研究业务上耗费精力,而将主要经历放在架构的研究上。 1.3 研究平台介绍 1.3.1 .NET 平台简介 .NET 这个名称涵盖了微软的主要开发平台。这个主题十分广泛,包含了许多 规范,如组件格式、编程语言、标准类库和工具等。它主要包括一下内容: .NET Framework(架构),包括:Common Language Runtime(CLR)(通用语 言运行环境),这是用于运行和加载应用程序的软件组件;新的类库,分级组织 了开发者可以在他们的应用程序中用来显示图形用户界面、访问数据库和文件以 及在 Web 上通信的代码集。 .NET 开发者工具,包括:Visual Studio .NET Integrated Development Environment (IDE)(Visual Studio .NET 集成开发环境),用来开发和测试应 用程序;.NET 编程语言(例如 Visual Basic .NET 和新的 Visual C#),用来创 3 烟台大学毕业论文(设计) 建运行在 CLR 下并且使用类库的应用程序。 ASP .NET,一个取代以前的 Active Server Pages (ASP)的特殊类库,用 来创建动态的 Web 内容和 Web 服务器应用程序,这些都将采用诸如 HTML、XML 和 Simple Object Access Protocol(SOAP)(简单对象访问协议)等Internet 协议和数据格式。 .NET framework 的组成如下: 图 1.1 .NET framework 结构图 1.3.2 ASP.NET 简介 ASP.NET 是统一的 Web 应用程序平台,它提供了为建立和部署企业级 Web 应用程序所必需的服务。ASP.NET 为能够面向任何浏览器或设备的更安全的、更 强的可升级性、更稳定的应用程序提供了新的编程模型和基础结构。 ASP.NET 是 Microsoft .NET Framework 的一部分,是一种可以在高度分布 的 Internet 环境中简化应用程序开发的计算环境。.NET Framework 包含公共 语言运行库,它提供了各种核心服务,如内存管理、线程管理和代码安全。它也 包含 .NET Framework 类库,这是一个开发人员用于创建应用程序的综合的、面 向对象的类型集合。 ASP.NET 提供了下面的优点: 可管理性: ASP.NET 使用基于文本的、分级的配置系统,简化了将设置应 用于服务器环境和 Web 应用程序的工作。因为配置信息是存储为纯文本的,因 此可以在没有本地管理工具的帮助下应用新的设置。配置文件的任何变化都可以 自动检测到并应用于应用程序。详细信息,请参阅 ASP.NET 配置。 安全: ASP.NET 为 Web 应用程序提供了默认的授权和身份验证方案。开发 人员可以根据应用程序的需要很容易地添加、删除或替换这些方案。详细信息, 请参阅 ASP.NET 安全。 易于部署: 通过简单地将必要的文件复制到服务器上,ASP.NET 应用程序 即可以部署到该服务器上。不需要重新启动服务器,甚至在部署或替换运行的已 编译代码时也不需要重新启动。详细信息,请参阅 ASP.NET 部署。 增强的性能: ASP.NET 是运行在服务器上的已编译代码。与传统的 Active 4 烟台大学毕业论文(设计) Server Pages (ASP) 不同,ASP.NET 能利用早期绑定、实时 (JIT) 编译、本机 优化和全新的缓存服务来提高性能。详细信息,请参阅 ASP.NET 性能监视。 灵活的输出缓存: 根据应用程序的需要,ASP.NET 可以缓存页数据、页的 一部分或整个页。缓存的项目可以依赖于缓存中的文件或其他项目,或者可以根 据过期策略进行刷新。 国际化: ASP.NET 在内部使用 Unicode 以表示请求和响应数据。可以为每 台计算机、每个目录和每页配置国际化设置。 移动设备支持: ASP.NET 支持任何设备上的任何浏览器。开发人员使用与 用于传统的桌面浏览器相同的编程技术来处理新的移动设备。 扩展性和可用性: ASP.NET 被设计成可扩展的、具有特别专有的功能来提 高群集的、多处理器环境的性能。此外,Internet 信息服务 (IIS) 和 ASP.NET 运行时密切监视和管理进程,以便在一个进程出现异常时,可在该位置创建新的 进程使应用程序继续处理请求。详细信息,请参阅 ASP.NET 进程隔离。 跟踪和调试: ASP.NET 提供了跟踪服务,该服务可在应用程序级别和页面 级别调试过程中启用。可以选择查看页面的信息,或者使用应用程序级别的跟踪 查看工具查看信息。在开发和应用程序处于生产状态时,ASP.NET 支持使用 .NET Framework 调试工具进行本地和远程调试。当应用程序处于生产状态时,跟踪语 句能够留在产品代码中而不会影响性能。 与 .NET Framework 集成: 因为 ASP.NET 是 .NET Framework 的一部分, 整个平台的功能和灵活性对 Web 应用程序都是可用的。也可从 Web 上流畅地访 问 .NET 类库以及消息和数据访问解决方案。ASP.NET 是独立于语言之外的,所 以开发人员能选择最适于应用程序的语言。另外,公共语言运行库的互用性还保 存了基于 COM 开发的现有投资。 与现有 ASP 应用程序的兼容性: ASP 和 ASP.NET 可并行运行在 IIS Web 服务器上而互不冲突;不会发生因安装 ASP.NET 而导致现有 ASP 应用程序崩溃 的可能。ASP.NET 仅处理具有 .aspx 文件扩展名的文件。具有 .asp 文件扩展 名的文件继续由 ASP 引擎来处理。然而,应该注意的是会话状态和应用程序状 态并不在 ASP 和 ASP.NET 页面之间共享。 1.3.3 Visual Studio 2005 简介 Visual Studio 是微软公司推出的开发环境。是目前最流行的 Windows 平 台应用程序开发环境。目前已经开发到 9.0 版本,也就是 Visual Studio 2008。 Visual Studio 可以用来创建 Windows 平台下的 Windows 应用程序和网络应 用程序,也可以用来创建网络服务、智能设备应用程序和 Office 插件。 本课题采用的 VS 版本为 Visual Studio 2005。 Visual Studio 2005 是微软在 2005 年发布的一个 VS 版本。.NET 字眼从各 种语言的名字中被抹去,但是这个版本的 Visual Studio 仍然还是面向 .NET 框架的(版本 2.0)。它同时也能开发跨平台的应用程序,如开发使用微软操作 系统的手机的程序等。总体来说是一个非常庞大的软件,甚至包含代码测试功能。 这个版本的 Visual Studio 包含有众多版本,分别面向不同的开发角色。同时 还永久提供免费的 Visual Studio Express 版本。 5 烟台大学毕业论文(设计) 2. 核心思想及总体架构 2.1 分层架构的关键性思想 在软件开发技术的发展过程中,出现了很多优秀的思想与模式。这些思想和 模式凝结了无数程序设计人员的实践经验,是软件开发领域的精华。其中的很多 思想,对分层架构设计也有着重要的指导作用,下面将列出一些在本课题研究中 起着重要作用的指导思想和所设计的架构应遵循的原则。 2.1.1 逐层调用原则及单向调用原则 现在约定将 N 层架构的各层依次编号为 1、2、…、K、…、N-1、N,其中层 的编号越大,则越处在上层。那么,我们设计的架构应该满足以下两个原则: 1.第 K(1> +Request() 图 4.2 Adapter 设计模式 如图 4.2 所示,有接口 Target,它含有方法Request。Client依赖接口 Target 而不依赖于具体类,这样就可以通过多态性,让Client 与任何实现 Target 接口 的类交互工作。而现在Client 需要和 Adaptee 交互,但Adaptee 没有实现 Target 接口,它里面实现 Request 方法的方法名叫做 SpecificRequest,这样 Client 就没法和 Adaptee 交互。因此,我们可以写一个适配器类 Adapter,它实现了 Target 接口,并且聚合了一个 Adaptee 实例,在它的 Request 方法里调用 adapteeObject 的 SpecificRequest 方法。这样,Client就可以通过这个适配器, 和 Adaptee 交互。 根据这个模式,可以对实体类做如下设计: IUserInfo <> NBe a r Us e r InfoSQLServerUserInfo NBearAutoGeneralUserInfo Client 图 4.3 用 Adapter 模式对实体类解耦 如图 4.3 所示,各层(图中的 Client)不再和具体的实体类耦合,而是仅与 接口 IUserInfo 耦合,所有实体类都实现这个接口。而对于不支持此接口的自动 生成实体类,则通过 Adapter(图中为 NBearUserInfo)模式进行适配,这样, 18 烟台大学毕业论文(设计) 实体类从顺利从各层解耦,从而解决上文提到的问题。 4.3.2.2 使用转换器对实体类解耦 在 4.3.2.2 中提到的通过 Adapter 模式对实体类解耦的方法,虽然在一定程 度上能解决问题,但是在某些情况下,这个方案可能并不适用。例如,如果表示 层用到了微软的 ASP.NET Ajax 框架,那么就会出现难题。 一般来说,当应用了 ASP.NET Ajax 框架后,可以自动在 JavaScript 端创建 与后台对应的实体类,而且在异步调用后台服务时,实体类之间可以自动转换, 这个转换机制是 ASP.NET Ajax 框架自带的,无需我们干预。它实际的原理是, 首先将 JavaScript 端的实体类序列化成 JSON 或 XML 格式,然后以文本流的形式 传入后台,后台再将其反序列化为对应实体类。从后来传送到前台的原理也是类 似的。从这里可以看出,这就要求后台的实体类是可以序列化的。然后,应用 Adapter 模式后,各层不再和具体实体类产生依赖,而是仅依赖一组接口,接口 是不可序列化的,这就使 Ajax 的应用产生了困难。 为了解决这个问题,可以使用另一种方案对实体类解耦,就是转换器。 一般来说,虽然不同的实体类有很大差别,但是由于表示的是同一种实体, 对应的也是同一张表,所以它们的属性基本是一样的,这样,我们就可以让各层 依然依赖具体的实体类,而在需要使用特殊实体类时,通过一个叫转换器的机制, 进行不同实体类之间的转换,从而将实体类的差别控制在某个层中,而不会蔓延 到整个系统中。它的实现示意图如下: UserInfo NBearUserInfo Converter +EntityToNBear() +NBearToEntity() NBearUserDALClient 图 4.4 用转换器对实体类解耦 如图 4.4 所示,其他层(Client)仍与 UserInfo 相耦合,这样 Ajax 应用就 可以顺利将实体类在前、后台代码中完成序列化。而当NBear 的数据访问层需要 用到特殊的实体类 NBearUserInfo 时,则通过 Converter 完成两种实体类的转化。 从而在数据访问层外,并不知道这种差别。 4.3.3 实体类的实现 因为系统中准备用到 ASP.NET Ajax 框架,所以决定采用转换器方案进行设 计。如此一来,在实体类设计阶段,不需考虑特殊实体类的情况,而仅编写简单 实体类即可。 本课题约定,实体类命名规则为“实体名+Info”。 以用户实体为例,UserInfo 的完整代码见附录一。 19 烟台大学毕业论文(设计) 4.4 接口设计 4.4.1 接口概述及其作用 这里的“接口”一词,特指在分层架构中底层向顶层开放的可调用方法,具 体到本课题的 Demo 中,特指数据访问层接口和业务逻辑层接口。 接口在技术上编写难度不大,但是其意义十分重大。总体来说,接口有着一 下几个作用: z 接口明确了各层次的职责。 z 接口决定了各个层次具体需要实现的功能。 z 接口形成了整个分层架构的骨架 z 接口暴露了层次的 API,为上层提供了依赖点。 因此,接口的设计实际上处在现实需求和程序实现之间,起到承上启下的用。 它决定了需求分析中的各个需求如何合理地映射成各个层次的不同方法。所以接 口的设计应该在需求分析的基础上进行。 4.4.2 数据访问层接口的设计 因为接口直接关系到层次的职责,所以,在设计数据访问层接口之前,需要 对数据访问层的职责进行明确。 在本课题中,将数据访问层职责叙述如下:数据访问层负责与数据源的交互, 负责数据的创建、删除、更新及查询工作。它不应该包含任何业务逻辑或可视性 元素,对它所处理数据的业务意义是“无知”的。它与数据库系统一起负责数据 完整性。 具体来说,数据访问层的接口一般包含以下几种类型的操作: 创建:在数据库中插入新记录,无返回值或返回表示操作状态的标志值。 删除:在数据库中删除符合条件的记录,一般无返回值或返回表示操作状态 的标志值。 更新:将数据库中符合条件的记录更新,一般无返回值或返回表示操作状态 的标志值。 单实体查询:从数据库中读出符合条件单条记录的信息,一般返回单个实体 类。 集合实体查询:从数据库中读出符合条件的多条记录的信息,一般返回实体 类集合。 函数查询:根据一定的函数规则,根据数据记录查询相应的函数值,如查询 某个表的记录数目,返回指定函数值。 以先行实体用户为例,根据需求分析,数据访问层接口设计如表 4.1 所示。 20 烟台大学毕业论文(设计) 表 4.7 用户数据访问层接口方法列表 需求 接口 参数 返回值 操作类 型 新用户注册 Insert 用户实体 类 表示是否成功 的布尔值 创建 删除用户 Delete 用户 ID 表示是否成功 的布尔值 删除 更新用户信息、 更改密码、 更改用户身份 或状态 Update 用户实体 类 表示是否成功 的布尔值 更新 按 ID 取得用户 信息 GetByID 用户 ID 用户实体类 单实体 查询 按用户名查询、 检查是否有同 名用户 GetByName 用户名 用户实体类 单实体 查询 登录 Login 用户名、 密码 用户实体类 单实体 查询 按身份查询 GetByStatus 身份 用户实体类集 合 集合实 体查询 取得所有注册 用户的数量 GetCount 无 所有用户记录 的数量 函数查 询 按身份取得所 有注册用户的 数量 GetCountByStatus 身份 指定身份用户 记录的数量 函数查 询 具体 IUserDAL 的实现代码请参考附录一。 4.4.3 业务逻辑层接口的设计 与数据访问层接口的设计一样,在设计业务逻辑层的接口前,首先应明确其 职责。 本课题将业务逻辑层的职责叙述如下:业务逻辑层负责完成与系统领域相关 的业务逻辑操作,实现过程中的数据访问操作通过调用数据访问层实现。它对业 务相关的数据有效性负责,但是不负责 UI 输入数据的有效性。业务逻辑层中不 能含有与显示相关的逻辑,不能决定或影响数据最终的呈现样式。 由于不同领域的业务逻辑差别很大,所以无法像数据访问层那样对接口操作 做出明确的分类。 在实际项目开发中,业务逻辑层接口的设计往往要和领域专家合作。而在本 课题的 Demo 中,由于 BBS 的领域业务大家都很熟悉,所以不用进行专门的领域 逻辑调研。 下面以用户实体为例,业务逻辑层接口设计如表 4.2 所示。 21 烟台大学毕业论文(设计) 表 4.8 用户业务逻辑层接口方法列表 需求 接口 参数 返回值 新用户注册 Register 用户实体类 表示是否成功的布尔 值 删除用户 Remove 用户 ID 表示是否成功的布尔 值 更新用户信息、 更改密码、 更改用户身份 或状态 UpdateInformation 用户实体类 表示是否成功的布尔 值 按 ID 取得用户 信息 GetByID 用户 ID 用户实体类 按用户名查询、 检查是否有同 名用户 GetByName 用户名 用户实体类 登录 Login 用户名、密 码 用户实体类 按身份查询并 分页 GetByStatusAndPage 身份,分页 参数 用户实体类集合 取得所有注册 用户的数量 GetCount 无 所有用户记录的数量 具体 IUserBLL 的实现代码请参考附录一。 4.5 IoC 容器及依赖注入机制的设计 4.5.1 依赖注入与控制反转 依赖注入(Dependency Injection)和控制反转(Inversion of Control) 是同一个概念。具体含义是:当某个角色(调用者)需要另一个角色(被调用者) 的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但 在具有依赖注入的系统里,创建被调用者的工作不再由调用者来完成,因此称为 控制反转。创建被调用者实例的工作通常由 Ioc 容器来完成,然后注入调用者, 因此也称为依赖注入。 具体到分层架构中,依赖注入可以这样理解:当上层类的需要调用下层类功 能时,不再是由上层类直接实例化下层类,而是通过IoC 容器获取一个下层类的 实例,然后注入到上层类中。 以用户实体的业务逻辑层调用数据访问层为例,在没有依赖注入机制的系统 中,用户的业务逻辑层类直接实例化数据访问层类,如图 4.5(a)所示。在加入 依赖注入机制后,实例化数据访问层的任务就交给了 IoC 容器,如图 4.5(b)所 示。 22 烟台大学毕业论文(设计) 用户业务逻辑层类 UserBLL 用户数据访问层类 UserDAL 创建 用户业务逻辑层类 UserBLL 用户数据访问层类 UserDAL IoC 容器 创建 注入 图 4.5(a) 非 IoC 耦合示意 图 4.5(b) IoC 耦合示意 这样做的好处是什么呢?前面已经提到过,在我们设计的分层架构中层次之 间一定不能出现具体耦合。如果按照图 4.5(a)的模式,业务逻辑层势必要实例 化具体的数据访问层类,这就造成了紧耦合。而在依赖注入机制下,业务逻辑层 可以只依赖数据访问层的接口,至于在运行时得到的是哪种数据访问层类,它并 不需要知道,他只需从IoC 中获得相应的类,然后调用它的方法完成任务就行了。 而 IoC 内部可以有一套配置机制,这样就可以根据不同的配置信息,动态决定实 例化那一种数据访问层类,从而实现了两个层次间的解耦。 4.5.2 IoC 容器的职责明确 本课题中,将 IoC 容器的职责叙述如下:IoC 容器负责根据配置信息,创建 不同的数据访问层及业务逻辑层实例,并将其注入到业务逻辑层与表示层中,从 而实现三个层次的解耦。 4.5.3 IoC 容器及依赖注入机制的具体实现 依赖注入机制的实现有两种途径,一种是使用现有的框架。如 J2EE 平台上 的 Spring 框架就可以很好的完成依赖注入功能,在.NET 平台上有 Spring.NET 等框架可以选择。另一种途径,就是自己编写相应的代码,完成依赖注入机制。 鉴于.NET 平台上的“反射”机制对依赖注入的实现非常方便,所以本课题中 的 Demo 将采用第二种方案,使用 Abstract Factory设计模式和反射机制完成依 赖注入的设计。 4.5.3.1 Abstract Factory 模式在依赖注入机制中的应用 Abstract Factory 模式是在依赖注入机制中广泛采用的设计模式,Spring 的 IoC 容器就采用了这个经典模式。它的中文译名叫做“抽象工厂”,其定义是这样的: 提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。 23 烟台大学毕业论文(设计) IFactory <> +CreateProduct1() +CreateProduct2() FactoryA +CreateProduct1() +CreateProduct2() FactoryB +CreateProduct1() +CreateProduct2() IProduct1 <> IProduct2 <> Product1A Product1B Product2A Product2B Client 图 4.6 Abstract Factory 设计模式 图 4.6 是 Abstract Factory 模式的示意图。IFactory 为工厂接口,它内部定义 了生产一系列产品的方法。其它所有工厂都必须实现这个接口,但它们生产的产 品系列是不一样的,而一系列产品的每一种产品都实现自同一个产品接口。这样, 客户(Client)仅需要依赖工厂接口和产品接口。如果配置文件决定实例化哪一 个工厂,则客户就能在运行时动态获得不同的产品系列,从而系统获得了依赖注 入的功能。 下面具体到本课题中,讨论抽象工厂在依赖注入机制中的应用。以数据访问 层注入到业务逻辑层为例(业务逻辑层注入到表示层的原理类似),先假设系统 仅有用户和帖子两个实体,并且我们的系统需要能访问 SQLServer 和 Oracle 两 个数据库,那么,系统中就需呀 SQLServerDAL 和 OracleDAL 两个数据访问层, 它们都含有两个数据访问类,分别是 SQLServerUserDAL、SQLServerPostDAL 和 OracleUserDAL、OraclePostDAL。此时,用户和帖子的业务逻辑层类 UserBLL 与 PostBLL 作为客户类,不应该与具体的数据访问层类耦合,而应该先定义接 口 IUserDAL 与 IPostDAL 接口,让业务逻辑层与这两个接口耦合。再设计 SQLServerDALFactory 与 OracleDALFactory,分别作为生成两种数据访问层的工 厂,最后通过配置信息,决定在业务逻辑层中实例化哪个工厂,其类图如图 4.7 所示。 24 烟台大学毕业论文(设计) IDALFactory <> +CreateUserDAL() +CreatePostDAL() SQLServerDALFactory +CreateUserDAL() +CreatePostDAL() OracleDALFactory +CreateUserDAL() +CreatePostDAL() IUserDAL <> IPostDAL <> SQLServerUserDAL OracleUserDAL SQLServerPostDAL OraclePostDAL UserBLL PostBLL 图 4.7 Abstract Factory 模式在 IoC 设计中的应用 4.5.3.2 .NET 平台下 Abstract Factory 模式的改进——反射工厂 Abstract Factory 模式虽然可以完成依赖注入的机制的设计,但是其本身也有 缺陷。从上文可以看出,就数据访问层而言,每个数据访问层实现都要对应一个 工厂,业务逻辑层当然也是如此。并且以后每当添加一个新的实现,都要写一个 相应的工厂。因此,当层次的实现过多时,整个系统就会存在很多工厂,这些工 厂不仅使代码变得臃肿,也难以维护。 .NET 平台上,有一种叫“反射”的机制,利用这种机制,可以简化 Abstract Factory 设计模式的代码。而利用反射机制简化后的 Abstract Factory 则可以叫做 Reflection Abstract Factory,也叫反射工厂。 反射就是动态发现类型信息的能力。它帮助程序设计人员在程序运行时利用 一些信息去动态地使用类型,这些信息在设计时是未知的,这种能力可以用于后 期绑定。反射还支持的更高级的行为,能在运行时动态创建新类型,并且对这些 新类型的操作进行调用。 有了反射机制,我们就可以从配置文件(一般是在 Web.config)读出配置信 息后,不是根据这个信息实例化相应的工厂,而是让反射工厂通过这些信息,动 态加载相应程序集中的类,这样每个层次只需要一个工厂就可以完成依赖注入, 即使以后出现新的实现,也不需要修改或添加工厂,只要在配置文件中写明程序 集的名字即可。如果考虑到性能和易用性,可以配合缓存机制和 Facade 设计模 式进一步优化 IoC 的设计。 本课题最终的 IoC 设计如图 4.8 所示,类关系如图 4.9 所示,具体实现代码 请参看附录一。 25 烟台大学毕业论文(设计) Web.config反射工厂 数据访问层接口 业务逻辑层接口 各种数据访问层实现 各种业务逻辑层实现 泛化 泛化 Cache 反射 Facade 使用 图 4.8 反射工厂结构图 DependencyProvider +GetDALObject() +GetBLLObject() CacheAccess +GetFromCache() +SaveToCahce() DALFactory BLLFactory IDAL <> IBLL <> Web.config 图 4.9 反射工厂示意图 在图 4.9 中 DALFactory 和 BLLFactory 的实例化箭头直接指向了 IDAL 和 IBLL。实际上,IDAL 和 IBLL 分别是一个工程,它里面是一组接口,这里不是指 工厂直接实例化接口(实际上接口也是不可能被实例化的)。 使用反射机制后,同一层次的不同实现分别放在不同工程下,而里面包含的 各个类的名字是一致的。例如 SQLServerDAL 下和 OracleDAL 下都有 UserDAL, 至于运行时具体实例化哪个,是通过 Web.config 中配置实现的。也就是说 Web.config 决定了实例化哪个工程下的 UserDAL。 26 烟台大学毕业论文(设计) 4.6 数据访问层的实现 4.6.1 数据访问层概述及实现要求 上文已经提到,数据访问层的职责是与数据源的交互,负责数据的创建、删 除、更新及查询工作。并且它不应该包含任何业务逻辑。 一般情况下,一个数据访问层对应一种数据库或一个数据访问框架。例如, 可以为 SQLServer、Oracle、MySQL 等数据库编写一个数据访问层,也可以编写 基于 NBear 或 NHibernate 的数据访问层,只要它们实现了数据访问层接口所定 义的功能,则可以相互替换,来支持不同的数据库或框架。 因为分层架构要求各层次职责单一且明确,所以,我们设计的数据访问层应 该是干净的、只关注于数据的访问。它包含的操作基本流程应该是“获取参数、 根据参数确定操作命令、执行命令、返回结果”。这里的根据参数确定操作命令, 可能是动态创建的 SQL 语句,也可能是存储过程,或者ORM 框架中指定的命令形 式;而返回结果可能是查询到的结果,也可能是操作影响的行数等标志性信息。 它所接受到的参数应该是能直接用来确定操作命令的,而不需要进行任何计算, 计算应该放在业务逻辑层里。而返回的结果也应该以基本数据类型、实体类或实 体类集合的形式返回,对数据不应该做任何处理和修饰。 下面将讨论基于朴素的实现和基于 ORM 框架的实现两种实现形式。 4.6.2 数据访问层的朴素实现 所谓数据访问层的朴素实现,就是指传统的通过执行 SQL 语句或调用存储过 程实现对数据库的操作。.NET 平台内置了丰富的对数据库进行操作的类库,因 此数据访问层的朴素实现非常方便。 具体到本课题中,数据访问层的朴素实现操作一般分为以下几个步骤。 使用动态 SQL 语句: 1.获取参数。 2.动态组合 SQL 语句。 3.执行 SQL 语句。 4.返回结果。 使用存储过程: 1.获取参数。 2.生成存储过程可用参数形式。 3.调用存储过程。 4.返回结果。 由于每次执行 SQL 语句和执行过程时,都需要一系列类似的操作,因此我们 可以将这个地方封装起来,这样可以大大减少重复性代码。在本课题中,将各种 封装对数据库操作的辅助类叫做 DALHelper 类,如对SQLServer 数据库进行操作 的辅助类就叫做 SQLServerHelper,它包含了四个方法:ExecuteSQLNonQurey、 ExecuteSQLReader、ExecuteProcedureNonQurey、ExecuteProcedureReader,分 别用于执行 SQL 语句不返回结果、执行 SQL 语句返回 DataReader,执行存储过 程不返回结果、执行存储过程返回 DataReader。这样,在 SQLServer 数据访问 层中,就可以直接调用这些方法完成操作。朴素实现的示意图如图 4.10 所示。 27 烟台大学毕业论文(设计) SQLServerHelper +ExecuteSQLNonQurey() +ExecuteSQLReader() +ExecuteProcedureNonQurey() +ExecuteProcedureReader() IUserDAL <> UserDAL UserDAL OracleHelper SQLServerDAL 工程下 OracleDAL 工程下 图 4.10 数据访问层的朴素实现 具体实现代码请参考附录一。 4.6.3 数据访问层的 ORM 实现 4.6.3.1 ORM 概述 对象-关系映射(Object/Relation Mapping,简称 ORM),是随着面向对象的 软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中 的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储 系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对 象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在 数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映 射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的 映射。 面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起 来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为 了解决这个不匹配的现象,对象关系映射技术应运而生。 字母 O 起源于"对象"(Object),而 R 则来自于"关系"(Relational)。几乎 所有的程序里面,都存在对象和关系数据库。在业务逻辑层和用户界面层中,我 们是面向对象的。当对象信息发生变化的时候,我们需要把对象的信息保存在关 系数据库中。 当你开发一个应用程序的时候(不使用 O/R Mapping),你可能会写不少数据 访问层的代码,用来从数据库保存,删除,读取对象信息,等等。你在 DAL 中写 了很多的方法来读取对象数据,改变状态对象等等任务。而这些代码写起来总是 重复的。 如果打开朴素实现的数据访问层代码,你肯定会看到很多近似的通用的模 28 烟台大学毕业论文(设计) 式。我们以保存对象的方法为例,你传入一个对象,为 SqlCommand 对象添加 SqlParameter,把所有属性和对象对应,设置 SqlCommand 的 CommandText 属性 为存储过程,然后运行 SqlCommand。对于每个对象都要重复的写这些代码。 除此之外,还有更好的办法吗?有,引入一个 O/R Mapping。实质上,一个 O/R Mapping 会为你生成 DAL。与其自己写 DAL 代码,不如用 O/R Mapping。你用 O/R Mapping 保存,删除,读取对象,O/R Mapping 负责生成 SQL,你只需要关心对 象就好。 对象关系映射成功运用在不同的面向对象持久层产品中, 如:Torque,OJB,Hibernate,TopLink,Castor JDO, TJDO 等。 一般的 ORM 包括以下四部分: z 一个对持久类对象进行 CRUD 操作的 API。 z 一个语言或 API 用来规定与类和类属性相关的查询。 z 一个规定 mapping metadata 的工具。 z 一种技术可以让 ORM 的实现同事务对象一起进行 dirty checking, lazy association fetching 以及其他的优化操作。 4.6.3.2 使用 NBear 实现 ORM NBear 是一个基于.Net 2.0、C#2.0 开放全部源代码的的软件开发框架类库。 NBear 的设计目标是尽最大努力减少开发人员的工作量,最大程度提升开发效 率,同时兼顾性能及可伸缩性。本课题的Demo 中就是使用 NBear 完成 ORM 操作。 使用 NBear 完成数据访问层和朴素的数据访问层实现在流程上有很大不同, 概括来说,NBear 的数据访问层设计可以分为以下几个步骤: 1.创建 NBear 设计工程,按 NBear 规定的格式编写设计类,完成后并将其编 译。 2.使用 NBear 提供的 EntityDesignToEntity 工具生成实体类代码、配置文 件和数据库脚本。 3.将实体类代码和配置文件保存到相应位置,用数据库脚本生成数据库,并 修改 Web.config 相应的配置信息。这里也可以从设计好的数据库生成设计类, 再从设计类生成实体类代码和配置文件。 4.使用 Gateway 访问数据库,完成数据访问层设计。 使用 NBear 后,由于 Gateway 访问数据库需要用到的实体类是自动生成的, 与我们的实体类不兼容,所以应该使用上文提过的实体类解耦方法对实体类解 耦,这里我们选用转换器。 下面以用户实体为例,说明基于 NBear 框架的 ORM 数据访问层实现。 如图 4.11 所示,基于 NBear 的 UserDAL 需要用到的实体类是自动生成的 t_User,但系统中标准的实体类是 UserInfo,所以在实体类和数据访问层中间, 有一个转换器 UserConvertor 负责两种实体类的转换。当接到 UserInfo 型的参 数时,由 UserConvertor 将其转换为 t_User 型,此时 NBear 的 UserDAL 就可以 调用 Gateway 中的方法操作数据库。当返回结果也是一样,Gateway 返回的结果 会以 t_User 的形式存在,此时UserDAL 调用 UserConvertor 将其转换为 UserInfo 型再返回,这样,从外界看来,不知道转换器的存在。 具体实现代码请参考附录一。 29 烟台大学毕业论文(设计) t_User <> UserInfo UserConvertor IUserDAL UserDAL NBearDAL 工程下 Gateway 图 4.11 基于 NBear 框架的数据访问层 ORM 实现 4.6.4 两种实现方法的比较 数据访问层的两种实现方法可以说是各有千秋,不能武断的说哪个好那个 差,而应该根据具体情况选择适当的方法。下面对两者的几个关键性方面进行一 个简要的比较。 z 性能:一般情况下,朴素实现的数据访问层在性能上由于 ORM 实现。 z 使用难度:朴素实现比较容易,因为其使用的都是.NET 平台内置类库。 而 ORM 实现需要专门学习 ORM 框架的使用。 z 代码质量:朴素实现下的代码较为臃肿,而 ORM 实现的代码比较优雅。 z 开发效率:朴素实现不需要过多的准备,但是后期重复性劳动很多,开 发效率地下;而ORM 在开始时期需要耗费时间和精力进行配置等预处理, 但是预处理完成后开发效率会高于朴素实现。 4.7 业务逻辑层的实现 4.7.1 业务逻辑层概述及实现要求 业务逻辑层负责领域相关业务的实现,在本课题提出的三层架构中起到承上 启下的作用。一般情况下,业务逻辑的设计需要和领域专家进行合作。业务逻辑 层中凡是需要进行数据库操作的地方都应该调用数据访问层完成,因此其中不应 该包含任何对数据库操作的代码,另外,业务逻辑层也不应该对数据的呈现形式 有任何影响,因为那是表示层的责任。业务逻辑层不需要对用户输入信息的有效 性负责,因为数据访问层应该对用户输入的信息进行验证,并保证传递给业务逻 辑层有效的数据,但是,对于业务数据,业务逻辑层应当负责。现将输入数据与 业务数据定义如下: 输入数据——是用户从 UI 界面输入的数据,如用户注册时的用户名、密码、 E-mail 等。 业务数据——根据一定业务规则确定的数据,如用户注册时初始积分为 0、 初始身份为注册用户等,这些数据不是用户通过 UI 输入的,一般也不会由用户 通过 UI 进行更改。 30 烟台大学毕业论文(设计) 4.7.2 业务逻辑层的应用模式 根据 Martin Fowler 在《企业应用架构模式》一书中对业务逻辑层的叙述, 业务逻辑层主要可以分为三种模式:Transcation Script、Domain Model和 Table Module。 4.7.2.1 Transcation Script 模式 Transcation Script模式是面向过程的,它的主要思想就是将业务逻辑的处 理看成一个过程。使用 Transcation Script 模式时,一般不需要数据访问层, 而是将数据库的访问代码直接写在业务逻辑层里。 Transction Script 模式主要适用于业务逻辑较为简单的系统。 4.7.2.2 Domain Model 模式 Domain Model 模式是典型的面向对象设计思想。它一般通过实体类或实体类 集合与数据访问类和表示层传递数据。并且它充分考虑到业务逻辑层的复杂多 变,引入了 Strategy 模式和依赖注入机制,并充分利用封装、继承、多态等面向 对象的特质来处理复杂多变的业务逻辑。 Domain Model 模式最大的难题在于 ORM,因为它所接受的发送的数据均是 以实体类形式进行的,而数据库需要关系结构,这样就需要进行 ORM,当然, 在设计良好的三层架构中,数据访问层负责了 ORM 过程而使得业务逻辑层可以 专注于领域逻辑,而不需要操心这些问题。 4.7.2.3 Table Module 模式 Table Module 模式与 Domain Model 很相似,但是它与数据访问层交互的数 据形式是 DataSet 形式,即它不是抽象了实体,而是抽象了数据表,这样就免去 了 ORM 的过程。但是使用 Table Module 模式,会丢失一部分面向对象的特性。 4.7.3 业务逻辑层的实现 由于本课题中业务逻辑比较简单,所以业务逻辑层的实现也比较简单,基本 是对数据访问层操作的封装,让表示层与数据访问层解耦。 在模式上,本课题的 Demo 使用的是 Domain Model 模式。 值得一提的是,虽然大多数业务逻辑操作都只涉及到一个实体的数据访问层 操作,但是也有设计多个实体的情况。如发布帖子时除了添加帖子外,也要对发 帖用户的积分进行更改。 31 烟台大学毕业论文(设计) 4.8 表示层的实现 由于近几年 Ajax 技术的兴起,表示层的设计思想有了翻天覆地的变化。在 这一节,将只讨论传统的同步模型下表示层的设计。至于基于 Ajax 的异步式设 计,将在第五章中单独讨论。 4.8.1 表示层概述及实现要求 表示层是一个系统的门面,它负责与用户交互。不论你的系统做的多么复杂, 最终用户看到的只是表示层的东西,所以,表示层的好坏,直接影响用户对系统 的影响。如果一个系统底层做得非常优秀,而表示层非常糟糕的话,一定不可能 得到用户的青睐。 除了 UI 部分,表示层也承担了许多功能性职责,如对用户输入数据的验证, Session 的管理以及页面之间的跳转逻辑等。 总体来说,本课题将表示层的职责明确如下:表示层负责接收用户的输入以 及将输入数据呈现给用户,并且决定呈现样式。它对用户输入数据的有效性负责, 同时管理会话及页面跳转等逻辑。进一步来说,本课题将表示层分为两部分:UI 和表示逻辑。 UI——即用户接口,它是真正的可视化部件。UI 中不应该包含任何逻辑性, 它仅仅决定呈现给用户的界面是什么样子,至于里面显示何种信息,则由表示逻 辑决定。在基于 ASP.NET 的系统中,UI 一般包括 aspx 文件、css 样式表文件以 及图片等元素。 表示逻辑——负责与表示有关的逻辑,如页面如何条件性跳转、根据用户的 输入进行数据验证,然后显示不同的信息、根据系统运行情况显示不同的信息等。 概括来说,UI 决定“怎么显示”,表示逻辑决定“显示什么”。 一个好的表示层,应该包括以下几个特点: z 美观。俗话说“佛靠金装,人靠衣装”,美观的UI 设计,是吸引用户的 决定性要素,毕竟爱美之心、人皆有之。 z 良好的用户体验。如果美观是为了给用户一个好的第一印象,那么良好 的用户体验则对留住用户至关重要,毕竟没有人原意在一个乱七八糟、 找不着北的系统上浪费时间。 z 良好的结构设计。在 UI 设计上做到表现与结构的分离。 z 干净的逻辑。它的逻辑应该只和“显示什么”有关,至于如何取得这些 数据,数据又应该经过何种计算和转换,那是其它层的职责。 4.8.2 表示层的应用模式 4.8.2.1 MVC 模式 MVC 是一种用于表示层设计的复合设计模式。M、V、C分别表示模型(Model)、 View(视图)、Controller(控制器)。它们的职责如下: 模型:用于存储应用中的数据及运行逻辑,是应用的实体。 视图:负责可视部分,用于与用户交互及呈现数据。视图只负责显示,不负 责将用户的操作行为解释给模型。 32 烟台大学毕业论文(设计) 控制器:负责将用户的行为解释给模型。根据指定的策略和用户的操作,调 用模型的逻辑。 图 4.12 MVC 模式 它们之间的交互有以下几种(如图 4.12 所示): 1.当用户在视图上做任何需要调用模型的操作时,它的请求将被控制器截 获。 2.控制器按照自身指定的策略,将用户行为翻译成模型操作,调用模型相应 逻辑实现。 3.控制器可能会在接到视图操作时,指定视图做某些改变。 4.当模型的状态发生改变时,将通过某种方式通知视图。 5.视图可以从模型获取状态,从而改变自己的显示。 4.8.2.2 Page Controller 模式 Page Controller模式是 MVC 模式的一个变体。默认情况下,Page Controller 模式中所描述的概念是在 ASP.NET 中实现的。ASP.NET 页面框架实现这些概念 所采取的方式使得在客户端上捕获事件、将其传输到服务器并调用适当方法这一 系列操作的基本机制是自动进行的,并且对实现者来说是不可见的。页面控制器 是可扩展的,因为它会在生命周期的特定点上公开各种事件,因此,与应用程序 具体相关的操作可以在适当的时候运行。它的基本原理如图 4.13 所示。 图 4.13 Page Controller 模式 33 烟台大学毕业论文(设计) 4.8.2.3 MVP 模式 ASP.NET 默认采用的 Page Controller 模式对应用程序分层和测试都没有提 供良好的支持,对ASP.NET 运行时的依赖使得测试必须在实际的应用场景中才可 以顺利进行,我们要寻找一种更好的模式以针对不同的视图页提高测试效率,MVP 就是这样一种方式。 Model-View-Presenter 旨在应用程序分层和提高测试效率,它的主要目标是 将显示逻辑与业务逻辑分离,正如我们设计面向对象程序中创建松散耦合并可重 用的对象。为了实现该目的,我们针对各种不同的业务创建不同的类和层,例 如:View, Presentation, Service和 Data-access。在 ASP.NET 中很容易将这种 业务逻辑添加到页面或用户控件类中,但同时造成紧耦合并降低了可重用性和测 试效率。MVP 通过使用 presenter 层将显示逻辑和控制逻辑相分离。 MVP 的另一个目标是提高针对 View 的测试效率。编写依赖 Session, ViewState, Ajax, HTML 或 web 控件和业务实体的单元测试类较为复杂,因此我 们将各视图的显示逻辑保留在 aspx/ascx 文件类中,并将业务逻辑从中分离出来 放在相应的类中,在 MVP 中 Presenter 充当视图和业务逻辑的缓冲层。 4.8.3 表示层的具体实现 在本课题的 Demo 中,表示层分为同步模型和异步模型。其中异步模型部分 将在第五章单独讨论,所以这里只对同步模型的实现进行叙述。 由于这个 BBS 系统的表示层不算复杂,并且没有使用测试驱动开发,为了充 分提高开发效率,这里采用了 Page Controller 模式。 View 的实现主要包括 aspx 文件、css 文件和图片。这里有两个设计要点。 第一是“代码分离”,即aspx 中不包含任何逻辑性代码,而仅作为页面的结构; 第二是表现与结构的分离,aspx 不仅不包含逻辑性代码,也不包括任何样式, 所有的呈现样式都定义在 css 样式表文件中,即做到表现与结构的分离。在布局 上,使用了 DIV+CSS 标准化布局。 Controller 就是页面的代码隐藏文件,即文件名为 XX.aspx.cs 的文件,其 中包含了控件事件的相应代码、页面初始化及跳转逻辑等表示逻辑。当然,它不 包含业务性逻辑,业务性逻辑均通过调用数据访问层完成。 Model 在这里可以看做业务逻辑层和数据访问层的全部,也可以看做业务逻 辑层的接口,这和观察角度有关。总之,Model 负责了 UI 和表示逻辑之外的所 有职责。 Default.aspx <> Default.aspx.css <> Default.aspx.cs <> Model 图 4.14 表示层的实现 34 烟台大学毕业论文(设计) 图 4.14 显示了本课题 Demo 中同步表示层的实现结构。 这个设计有以下几个优点: z 充分利用了 ASP.NET 的“代码隐藏”技术,实现 Page Controller 模式 非常自然,提高了开发效率。 z 将 View 的结构与表现分离,aspx 文件只保存页面结构,而表现全部放 在 css 样式表中。这样做的直接好处就是使代码更为清晰、样式的复用 和修改更加方便,而对整体的换肤也更加容易。 z 代码隐藏技术使得 aspx 文件中不包含逻辑性代码,从而使其成为一个干 净的结构,这对提高系统的可维护性非常有利。 当然,如果在表示层更加复杂的系统中或使用测试驱动开发,可以考虑使用 完整的 MVC 模式或 MVP 模式,藉此进一步降低耦合、提高测试效率。 4.9 辅助类的实现 4.9.1 辅助类概述 本课题所说的辅助类是指这样一些类,它们的存在不是为了抽象现实的实体 或者实现面向对象的思想,而仅仅是将一些同类别的操作封装起来,方便代码复 用。从这个意义上说,之前提到的工厂、DALHelper 类和缓存操作类都输入辅助 类的范畴。一般来说,辅助类具有一下特点: z 不代表任何实体或实体操作功能的面向对象抽象。 z 不属于任何一个层次。 z 在多个部分、多个时段被使用。 z 全局仅需要一个实例。 其中最后一个特点决定了辅助类设计的特别之处。我们知道,一般的类必须 实例化后才能使用。而类的示例通常分配于系统的堆之上,堆是一个特殊的名词, 它代表一片可用的内存区域。 上面提到,辅助类在多个部分多个时段被使用,但是由于其特殊性,每个辅 助类仅需要一个实例就能完成全部任务。如果产生多个辅助类的实例,无异于对 内存的浪费,而且频繁的创建、销毁操作极易造成内存泄露。这就需要我们寻找 一种方法,实现同一个辅助类在全局仅存在一个实例。下面讨论了实现这个需要 的两种常用方法。 4.9.2 辅助类全局唯一实例的实现模式 4.9.2.1 Singleton 模式 Singleton 模式的定义为:确保一个类只有一个实例,并提供全局访问点。 通过上面的模式意图可以看出,这个模式对于上一节提到的难题正好是对症 下药。具体到.NET 平台,实现 Singleton 模式的具体步骤如下: 1.将需要实现 Singleton 模式的类的构造函数设为私有,防止外部实例化。 2.自聚合一个静态成员,成员类型为自身的实例。 3.提供取得全局唯一实例的静态方法。 图 4.15 为 Singleton 模式的示意图。 35 烟台大学毕业论文(设计) SingletonClass -singletonClassObject -SingletonClass() +GetInstance() 图 4.15 Singleton 模式 如图 4.15 所示,SingletonClass 的构造函数被设为私有,这样就无法从外部 实例化这个类。而其提供了一个叫做 GetInstance 的静态方法,当需要获得 SingletonClass 实例时,就调用这个静态方法,它会负责检查内部的 singletonClassObject 是否没有初始化,如果没有,它将创建一个自身实例,赋值 给 singletonClassObject ,如果 singletonClassObject 已经存在,则将这个 singletonClassObject 返回给调用者。如此一来,无论在哪里、调用多少次 GetInstance,都可以保证获取到全局唯一的实例。 4.9.2.2 静态密封类 另一种辅助类的设计方案,就是将辅助类的全部方法都设置为静态方法,这 样每次使用辅助类时,不需要进行实例化,只要使用“类名.方法名”的语法就 可以完成对辅助类的访问。而一般辅助类是不能被继承的,所以在 C#中通常用 “sealed”关键字修饰辅助类,表示其是密封类,不能继承。因此本课题将这种 设计方案称为“密封静态类”。 本课题的 Demo 中,辅助类的实现全部使用了密封静态类。 4.9.2.3 两种设计方案的比较 由于缺少足够的资料和试验,这里不打算从性能和效率方面对两者进行比 较,而仅从使用特性上对两者加以比较。它们的主要区别如下: z 静态密封类不保存状态,仅提供功能。而 Singleton 模式可以保存状态。 z 静态密封类不具备多态性,而实现 Singleton 模式的类可以有子类。 z Singleton 模式下获得的全局实例是对象,而静态密封类仅仅是方法的集 合。 在本课题中,建议使用静态密封类,因为这样可以免去获取实例这一步,而 直接使用方法,从而使代码更加简介。当然,如果需要实现多态性,则只能使用 Singleton 模式了。例如,如果系统中不使用反射工厂而使用传统的抽象工厂, 由于不同工厂需要实现多态性,则只能采用 Singleton 设计模式。 36 烟台大学毕业论文(设计) 5. 基于 Ajax 异步模型的表示层 5.1 基于 Ajax 技术的异步模型 5.1.1 Ajax 概述 Ajax 这一术语是短语 Asynchronous JavaScript and XML,中文译作“异步式 JavaScript 与 XML”,它是近几年出现,并迅速红遍大江南北的一项技术。 严格来说,Ajax 不是一个新技术,而是几种已有技术的组合,或者也可以说 是一种模式,它主要包括一下项技术: z 基于 web 标准的 XHTML+CSS 的表示。 z 使用 DOM(Document Object Model)进行动态显示及交互。 z 使用 XML 和 XSLT 进行数据交换及相关操作。 z 使用 XMLHttpRequest 进行异步数据查询、检索。 传统的 web 应用允许用户填写表单,当提交表单时就向 web 服务器发送一个 请求。服务器接收并处理传来的表单,然后返回一个新的网页。这个做法浪费了 许多带宽,因为在前后两个页面中的大部分 HTML 代码往往是相同的。由于每 次应用的交互都需要向服务器发送请求,应用的响应时间就依赖于服务器的响应 时间。这导致了用户界面的响应比本地应用慢得多。 与此不同,Ajax 应用可以仅向服务器发送并取回必需的数据,它使用 SOAP 或其它一些基于 XML 的 Web Service 接口,并在客户端采用 JavaScript 处理来自 服务器的响应。因为在服务器和浏览器之间交换的数据大量减少,结果我们就能 看到响应更快的应用。同时很多的处理工作可以在发出请求的客户端机器上完 成,所以 Web 服务器的处理时间也减少了。 Ajax 应用程序的优势在于: 1.通过异步模式,提升了用户体验。 2.优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽 占用。 3. Ajax 引擎在客户端运行,承担了一部分本来由服务器承担的工作,从而减 少了大用户量下的服务器负载。 5.1.2 Ajax 技术的实现原理 客户端 服务端 表单数据、URL请求 整个页面 图 5.1 传统同步模型下请求示意图 37 烟台大学毕业论文(设计) 客户端 服务端 XMLHttpRequest 回调函数 请求数据及请求 上下文 响应数据 图 5.2 Ajax 异步模型请求示意图 图 5.1 显示了在传统的同步模型情况下,B/S 架构的系统是如何工作的。在 这种架构下,客户端发出请求,可能是单纯请求一个新页面,也可能是提交了包 含数据的表单。不论怎样,这些请求信息将被提交到服务端,在从提交开发,客 户端就进入“假死”状态,服务端收到请求信息及数据,按照指定的程序对其进 行处理,然后把响应信息交给客户端,这些响应信息是完整的页面。客户端接到 响应信息后,把它们显示在浏览器中。 图 5.2 是基于 Ajax 技术异步模型下 B/S 系统的工作原理图。在这里,客户 端不再直接向服务端发出请求,而是通过一个 XMLHttpRequest 对象发送一个异 步请求,而后,服务端接收到请求和数据,开始按照程序进行处理,而此时的客 户端不必等待,可以继续做自己的工作。当服务端完成处理后,将自动调用一个 在发送请求时指定的回调函数,这个回调函数一般使用JavaScript 对页面的 DOM 进行操作,完成页面的更新。而整个这个过程是不需要刷新页面的。 5.2 ASP.NET AJAX 框架 5.2.1 ASP.NET AJAX 概述 ASP.NET AJAX 是微软公司官方发布的用于.NET 平台的 Ajax 框架,目前最 高版本为 1.0 正式版。可以说这个框架是目前对 Ajax 技术最完备的封装,它的 出现使 ASP.NET 上的 Ajax 应用编写变得简单很多。概括来说,它主要实现了以 下特性: z 对 JavaScript 进行了面向对象扩展,使得用 JavaScript 也可以方便使用类、 对象、接口、多态等面向对象元素,它的组件思想可以让我们非常方便 地利用面向对象的思想实现 Ajax 网页技术。 z 对 JavaScript 的某些数据类型进行了扩展,对常用操作进行了封装。 38 烟台大学毕业论文(设计) z 提供了服务端组件,使得开发人员可以在服务端使用熟悉的方式进行 Ajax 应用的开发。这样,即使不了解 Ajax 的开发人员也可以用类似服 务器端编程的开发模式完成 Ajax 应用的开发。 z 浏览器兼容层提供了对跨浏览器的支持,使得开发人员从繁琐的跨浏览 器代码编写中解脱出来。 目前的 ASP.NET AJAX 包含三个主要的部分。 ASP.NET 2.0 AJAX Extensions 1.0——这是 ASP.NET AJAX 框架的主要部 分,包括所有 ASP.NET AJAX 的核心 JavaScript 库文件和一组核心的服务器端组 件。 ASP.NET AJAX Control Toolkit——这是一个属于微软官方,但目前主要由 社区负责维护的服务器端组件库,里面包含了很多常用的Ajax服务器端组件。 ASP.NET Futures CTP——这是微软官方推出,但目前还没有正式发布的预 览部分。里面包含了一些扩展的客户端脚本、客户端组件的封装,还包含了一种 新式的Ajax开发方式:XML脚本语言开发。 5.2.2 编程模型的选择 ASP.NET AJAX框架下的开发模型分为服务器端编程和客户端编程两种,这两 种开发模型都能完成最终的功能,但是在一些方面差异较大,因此在本课题的研 究过程中,有必要对两种开发模型进行简要的分析,然后进行一个选择。 服务器端模型的编写方式与传统的 ASP.NET 开发模型很类似,常用功能已经 被封装成服务器端控件,需要使用时,直接拖到 Web 窗体上,就可以实现相应功 能,例如使用 UpdatePanel 控件实现页面的无刷新更新。 客户端模型的开发模式更类似于原始的 Ajax 应用开发,它需要手工编写 JavaScript 代码。与原始 Ajax 开发不同的是,使用的 JavaScript 代码不再是 传统的 JavaScript,而是经过扩展的,具有面向对象特性的JavaScript。ASP.NET AJAX 框架同时封装了常用功能,并且提供了对跨浏览器、使用代理模式直接调 用后台代码以及实体类自动转换的支持,使得客户端开发更加高效、容易。 而两种模型的主要差别,可以从以下几个地方体现出来: z 功能——服务端编程局限于已有控件,而客户端编程可以使用全部框架 的功能。 z 性能——客户端编程的性能优于服务端编程。 z 控制粒度——服务端编程的粒度较大,而客户端编程可以在更精细的粒 度上进行控制。 z 开发难度——服务端编程的开发模式与传统 ASP.NET 开发相似,上手简 单,客户端编程需要手工编写 JavaScript,需要开发者对 JavaScript、 DOM 操作及框架本身的很对底层机制有所了解,开发难度大。 z 开发效率——服务端编程主要靠“拖放式”开发,效率高,客户端编程 需要编写大量的代码,而且由于JavaScript 不利于调试,因此开发效率 较低。 从以上分析可以看出,总体来说服务端编程适合对性能和质量要求不高、而 比较注重开发效率的场合。而客户端编程适合对性能和质量要求较高的场合,当 然,这种模型对开发人员水平的要求也较高。 对两种模型的特性及各自优劣分析完后,我们应该继续分析在本课题的分层 39 烟台大学毕业论文(设计) 架构中应用 Ajax 究竟要达到什么目的,借此选择合适的开发模型。 本课题中,在表示层引入 Ajax 技术的目的有两个: 1.提高用户体验。 例如,用户注册时的用户名是不能重复的,这样,在注册的时候就需要对数 据库中是否存在重名用户进行判断。 在传统模型中,判断流程是这样的:提交表单数据(刷新)、后台进行检查、 返回结果交予浏览器显示(刷新)。可以看到,整个过程需要刷新两次,。用户体 验差。如果多次输入的用户名都存在重复,由于不能得到即使相应,整个注册过 程可能需要刷新很多次,而且这还只是一个用户名的检验。 换到 Ajax 模型后,过程是这样的:当输入用户名的文本框失去焦点时,触 发一个失去焦点事件,在这个事件中,将执行一段Ajax 代码,由XMLHttpRequest 发送一个异步请求,后台进行验证。在这段时间,用户可以继续其他内容的输入, 当检验完成后,回调函数将使用DOM 操作把验证结果显示在相应位置。整个过程 无刷新,而且用户可以快速得到响应。用户体验好。 2.节约带宽,减轻服务器的压力。 使用 Ajax 的目的当然不仅仅是无刷新,将这个页面放在一个 UpdatePanel 里是毫无意义的。使用Ajax 的另一个主要目的就是将表示逻辑全部放在客户端, 这样服务端给客户端返回的响应数据就可以仅包含数据,而无任何格式信息。而 显示格式由回调函数操作 DOM 进行实现。这样,服务端就不需传输那些重复性极 高的 HTML 标签,从而大大节约了带宽。 以登录操作且登录成功这个用例为例。设总带宽耗费为: 整个过程的响应字节数整个过程的发送字节数总带宽耗费 /= 在传统同步模型下,登录成功时的发送数据为用户名和密码,大约占 30 字 节,返回数据为整个登录成功提示页面,大约 2006 字节。这样总带宽耗费为 2006+30=2036 字节。 异步模型下,登录成功时的发送数据为用户名和密码,大约占 30 字节,返 回数据为表示成功的布尔值或字符串,大约10 字节,总带宽耗费30+10=40 字节。 带宽节约率为: 0.980062006/401 ≈−=带宽节约率 可以看出,单是一个登录,带宽节约率就高达 98%。所以,在适当的地方使 用 Ajax 技术,是可以大大节约带宽的。 综上分析,在本课题提出的分层架构中,Ajax需要对系统进行小粒度的精确 性控制,并且对性能要求很高,这样的要求是服务端编程无法胜任的,因此此处 采用客户端编程模型。 40 烟台大学毕业论文(设计) 5.3 以 JavaScript 为 Controller 的新 MVC 模型的提出 5.3.1 Ajax 给表示层带来的新问题 在第四章已经探讨过同步模型下表示层的实现。在这个实现中,页面的隐藏 代码文件(XXX.aspx.cs)负责了控制器的操作,它负责调用业务逻辑层,并决 定表示逻辑。 但是,当换到异步模型时,情况发生了改变。客户端请求的数据不再经过代 码隐藏文件,而是由 XMLHttpRequest 对象进行发送,并由回调函数负责对相应 信息的处理,这样,代码隐藏文件失去了作为控制器的效力,于是前面所属的实 现方法也就不再适用。 5.3.2 以 JavaScript 为控制器的 MVC 模型 说到底,Ajax应用的本质是 JavaScript 代码。JavaScript 代码可以放在 aspx 页面里面,也可以放在单独的 js 文件中。在这里,推荐后者。本课题中所有的 JavaScript 都是放在单独的 js 文件中的,这样可以使代码结构更清晰,也有利 于复用。 基于以上分析,并结合 ASP.NET AJAX框架的特性。在这里提出一种新的 MVC 模型(实际上是 Page Controller模型)。这种模型的架构方法是:以 aspx、css 样式表与图片等为 View,以业务逻辑层为Model,以与aspx 页面同名的 js 文件 为控制器。它的详细架构如图 5.3 所示。 View (Default.aspx) Controller (Default.aspx.js) WebService Model 触发事件 发送异步请求 调用业务逻辑层 返回响应数据 调用回调函数 JSON 序列化 实体类 JSON 序列化 实体类 图 5.3 以 JavaScript 为控制器的 MVC 原理图 从图 5.3 可以看出,这个模型和传统的 Page Controller 很类似,只是 js 文件代替了原来的 asps.cs 文件,并且控制器不是直接调用 Model,而是经过了 WebService 的过渡。下面对这个模型的关键点进行解析。 41 烟台大学毕业论文(设计) 在这里 js 文件与原来的代码隐藏文件功能很类似。同样 aspx 中不应该存放 任何表示逻辑,逻辑全部放在 js 文件中。与原来不同的是,传统模型中的事件 机制利用了.NET 平台的 WebForm 机制,利用服务器端控件的事件处理机制已一 种类似 WindowsForm 的编程模式进行。而 Ajax 中,是通过客户端表单组件触发 客户端事件,由 JavaScript 事件机制负责处理。 本课题提出的异步 MVC 模型 Controller 不是直接调用 Model,而是经过了 WebService 的过渡,这主要是因为 Ajax 应用的一些特点和 ASP.NET AJAX 框架 设计决定的。 首先就 ASP.NET AJAX 框架来说,它采用 Proxy 模式,自动在客户端生成服 务端的 JavaScript 代理,这样就可以在客户端通过“类名.方法名”的格式调用 后台方法,看起来就像直接在后台直接调用一样,非常方便。而 ASP.NET AJAX 框架的设计使得这个后台类可以是普通类,也可以是 WebService。为了实现这 种 Proxy 模式的自动生成,需要在后台类加入一些属性(Attribute),为了保持 Model 中类的干净,不建议在这些类中直接加入,所以,最好是用 WebService 封装一遍。 另外,js 作为控制器时,由于 JavaScript 本身的限制,它的某些逻辑需要 移到后台实现,如访问 Session 等,这就需要 WebService 作为一个过渡体,实 现这些控制器需要而 JavaScript 又实现不了的功能。 再者,由于 Ajax 的模式限制,我们在调用有返回值的后台方法时无法利用 传统的“变量名=类名.方法名”的格式直接实现,而必须在调用后台方法时同时 指定回调函数,然后在回调函数中在接收到返回值。如此一来,如果某个表示逻 辑需要调用多个后台方法,那就要写多个回调函数,代码极为冗余。所以我们可 以首先把这些方法封装到 WebService 的一个方法中,然后在JavaScript 中只需 一次调用就可以了,也只需要一个回调函数。其实,这里用到了 Facade 设计模 式。 从图 5.3 中还可以看到,在 JavaScript 与后台之间需要使用实体类进行数 据的传输。它的基本原理是:在客户端生成与服务端一致的JavaScript 实体类, 在传输之前,想将其 JSON 序列化,然后把序列化后的字符串传入另一端,在另 一端接受到序列化字符串后,再反序列化为实体类。这是个非常复杂的过程,庆 幸的是,ASP.NET AJAX 已经为我们实现了这一套功能,所以,我们仅需很少的 配置、甚至不需配置就可以完成实体类的序列化传输和反序列化。 5.4 异步表示层的实现 本课题的 Demo 中多处用到了异步模型表示层,基本原理如上文所分析。其 中利用 ASP.NET AJAX 的组件编程技术将常用的功能写成了客户端组件。具体实 现请参看附录一。 42 烟台大学毕业论文(设计) 结束语 经过三个月的奋战,毕业设计终于结束了。 说实话当初选这个题目的时候,心里有些没底,毕竟我从 PHP 平台转入.NET 平台的时间很短,并且没有在.NET 平台上开发大型项目的经验。在这种情况下, 进行.NET 平台上的架构研究非常困难。然而,我又不愿意做一个简单的系统, 而是希望能在这大学四年最后一份作业中充分展示出我四年的所学,同时也想给 自己一个挑战。 庆幸的是,软件设计和架构很多方面都是相通的,以前的很多经验,在这次 毕业设计中起了很大的作用。 在这次毕业设计的研究及开发过程中,我对.NET平台的很多原理、技术有了 更深入的认识,尤其是对 ASP.NET AJAX 框架的应用使我对这一崭新的框架有了 全面的认识。另外,这次研究让我对分层架构和设计模式的认识也提高到了一个 新的高度。 这次毕业设计的效果是令我满意的,它将成为一个支点,激励我在以后研究 生的学习中更加深入地研究软件开发与架构技术。 43 烟台大学毕业论文(设计) 致 谢 我要对我的指导教师王建华老师表示我最诚挚的感谢。从大二进入实验室以 来,您对我的直接指导、鼓励、以及潜移默化的影响,都对我在软件开发方面的 进步起到了至关重要的作用。 感谢王立宏老师和娄兰芳老师,谢谢您们在毕业设计的进行过程中对我提出 的意见和建议,它们对我更好地完成毕业设计起到不可替代的作用。 感谢所有曾经教过我的老师,是你们将营养灌输给我,让我茁壮成长。 感谢我的女朋友,谢谢你在我毕业设计的进行过程中给我的鼓励和笑容,这 让我觉得做毕业设计的日子不是那么枯燥和孤独。 44 烟台大学毕业论文(设计) 参考文献 [1] Eric Freeman 等,Head First 设计模式(中文版),中国电力出版社,2007 年 9 月 [2] 甄镭,.NET 与设计模式,电子工业出版社,2005 年 6 月 [3] 张逸,软件设计精要与模式,电子工业出版社,2007 年 4 月 [4] Ryan Asleson 等,Ajax 基础教程,人民邮电出版社,2006 年 2 月 [5] Dave Crane 等,Ajax 实战,人民邮电出版社,2006 年 4 月 [6] Steve McConnell,代码大全(第二版),电子工业出版社,2006 年 7 月 [7] 李超,CSS 网站布局实录,科学出版社,2006 年 9 月 [8] 陈黎夫,ASP.NET AJAX 程序设计-第 I 卷,人民邮电出版社,2007 年 4 月 [9] 陈黎夫,ASP.NET AJAX 程序设计-第 II 卷,人民邮电出版社,2007 年 10 月 [10] 郑耀东,ASP.NET 2.0 的 Web2.0 应用,人民邮电出版社,2007 年 9 月 [11] Jeffrey Zeldman,网站重构,电子工业出版社,2006 年 1 月 [12] Patrick Smacchia,C#和.NET2.0 实战,人民邮电出版社,2008 年 1 月 [13] Jesse Liberty 等,Programming in ASP.NET,电子工业出版社,2007 年 1 月 [14] ASP.NET 官方官方,www.asp.net [15] ASP.NET AJAX 官方网站,ajax.asp.net [16] CSDN,www.csdn.net [17] 博客园,www.cnblogs.com [18] Google,www.google.com [19] MSDN,msdn.microsoft.com 45 烟台大学毕业论文(设计) 附录一:核心代码摘要 用户实体类:UserInfo.cs using System; namespace NBBS.Entity { /// /// 实体类——用户 /// [Serializable] public class UserInfo { /// /// 私有成员变量 /// private int _id; private string _name; private string _password; private string _email; private int _point; private DateTime _date; private string _status; private string _state; private string _realName; private string _from; private string _tel; private string _qq; private int? _age; private string _sex; private string _sign; /// /// 属性 /// public int ID { get { return this._id; } set { this._id = value; } } public string Name { get { return this._name; } set { this._name = value; } 46 烟台大学毕业论文(设计) } public string Password { get { return this._password; } set { this._password = value; } } public string Email { get { return this._email; } set { this._email = value; } } public int Point { get { return this._point; } set { this._point = value; } } public DateTime Date { get { return this._date; } set { this._date = value; } } public string Status { get { return this._status; } set { this._status = value; } } public string State { get { return this._state; } set { this._state = value; } } public string RealName { get { return this._realName; } set { this._realName = value; } } public string From { get { return this._from; } set { this._from = value; } } public string Tel { get { return this._tel; } 47 烟台大学毕业论文(设计) set { this._tel = value; } } public string QQ { get { return this._qq; } set { this._qq = value; } } public int? Age { get { return this._age; } set { this._age = value; } } public string Sex { get { return this._sex; } set { this._sex = value; } } public string Sign { get { return this._sign; } set { this._sign = value; } } public UserInfo() { } } } 用户数据访问层接口:IUserDAL.cs using System; using System.Collections; using System.Collections.Generic; using NBBS.Entity; namespace NBBS.IDAL { /// /// 数据访问层接口——用户 /// public interface IUserDAL { /// /// 插入一个用户 /// /// 用户实体类 48 烟台大学毕业论文(设计) /// 操作是否成功 bool Insert(UserInfo user); /// /// 删除一个用户 /// /// 欲删除用户的 ID /// 操作是否成功 bool Delete(int id); /// /// 更新用户信息 /// /// 用户实体类 /// 操作是否成功 bool Update(UserInfo user); /// /// 按 ID 取得一个用户的全部信息 /// /// 用户 ID /// 用户实体类 UserInfo GetByID(int id); /// /// 按身份取得用户信息 /// /// 身份 /// 用户实体集合类 IList GetByStatus(string status); /// /// 取得全部记录的数量 /// /// 记录的数量 int GetCount(); /// /// 取得指定身份记录的数量 /// /// 身份 /// 记录的数量 int GetCountByStatus(string status); } 49 烟台大学毕业论文(设计) } 用户业务逻辑层接口:IUserBLL.cs using System; using System.Collections; using System.Collections.Generic; using NBBS.Entity; using NBBS.IDAL; namespace NBBS.IBLL { /// /// 业务逻辑层接口——用户 /// public interface IUserBLL { /// /// 注册 /// /// 实体类 /// 是否成功 bool Register(UserInfo user); /// /// 删除用户 /// /// 欲删除用户的 ID /// 是否成更 bool Remove(int id); /// /// 更新用户信息 /// /// 实体类 /// 是否成功 bool UpdateInformation(UserInfo user); /// /// 按 ID 取得某用户信息 /// /// 用户 ID /// 实体类 UserInfo GetByID(int id); 50 烟台大学毕业论文(设计) /// /// 按用户名取得一个用户的全部信息 /// /// 用户名 /// 用户实体类 UserInfo GetByName(string name); /// /// 用户登录 /// /// 用户名 /// 密码 /// 实体类 UserInfo Login(string name, string password); /// /// 按身份及分页信息取得用户 /// /// 身份 /// 页号 /// 每页显示多少条信息 /// 用户实体类集合 IList GetByStatusAndPage(string status,int pageNumber,int itemsPerPage); /// /// 按身份取得用户的数量,如果为 null 则取得所有用户数量 /// /// 身份 /// 用户数量 int GetCount(string status); } } 依赖注入提供类:DependencyProvider.cs using System; using System.Configuration; using System.Reflection; using System.Web; using System.Web.Caching; namespace NBBS.Factory { /// 51 烟台大学毕业论文(设计) /// 依赖注入提供者 /// 使用反射机制实现 /// public sealed class DependencyProvider { /// /// 取得数据访问层对象 /// 首先检查缓存中是否存在,如果不存在,则利用反射机制返回对象 /// /// 数据访问类名称 /// 数据访问层对象 public static object GetDALObject(string className) { /// /// 取得数据访问层名称,首先检查缓存,不存在则到配置文件中 读取 /// 缓存依赖项为 Web.Config 文件 /// object dal = CacheAccess.GetFromCache("DAL"); if (dal == null) { CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config")); dal = ConfigurationManager.AppSettings["DAL"]; CacheAccess.SaveToCache("DAL", dal, fileDependency); } string dalName = (string)dal; /// /// 取得数据访问层对象,首先检查缓存,不存在则利用反射机制 加载 /// 缓存依赖项为 Web.Config 文件 /// string fullClassName = dalName + "." + className; object dalObject = CacheAccess.GetFromCache(className); if (dalObject == null) { CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config")); dalObject = Assembly.Load(dalName).CreateInstance(fullClassName); CacheAccess.SaveToCache(className, dalObject, fileDependency); 52 烟台大学毕业论文(设计) } return dalObject; } /// /// 取得业务逻辑层对象 /// 首先检查缓存中是否存在,如果不存在,则利用反射机制返回对象 /// /// 业务逻辑类名称 /// 业务逻辑层对象 public static object GetBLLObject(string className) { /// /// 取得业务逻辑层名称,首先检查缓存,不存在则到配置文件中 读取 /// 缓存依赖项为 Web.Config 文件 /// object bll = CacheAccess.GetFromCache("BLL"); if (bll == null) { CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config")); bll = ConfigurationManager.AppSettings["BLL"]; CacheAccess.SaveToCache("BLL", bll, fileDependency); } string bllName = (string)bll; /// /// 取得业务逻辑层对象,首先检查缓存,不存在则利用反射机制 加载 /// 缓存依赖项为 Web.Config 文件 /// string fullClassName = bllName + "." + className; object bllObject = CacheAccess.GetFromCache(className); if (bllObject == null) { CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config")); bllObject = Assembly.Load(bllName).CreateInstance(fullClassName); CacheAccess.SaveToCache(className, bllObject, fileDependency); 53 烟台大学毕业论文(设计) } return bllObject; } } } 数据访问层工厂:DALFactory.cs using System; using NBBS.IDAL; namespace NBBS.Factory { /// /// 数据访问层工厂,用于生成相应的数据访问层对象 /// 使用 Abstract Factory 设计模式+Facace 设计模式+反射机制+缓存机 制设计 /// public sealed class DALFactory { /// /// 获取用户数据访问层类 /// /// 用户数据访问层 public static IUserDAL CreateUserDAL() { return (IUserDAL)DependencyProvider.GetDALObject("UserDAL"); } /// /// 获取登录数据访问层类 /// /// 登录数据访问层 public static ILoginDAL CreateLoginDAL() { return (ILoginDAL)DependencyProvider.GetDALObject("LoginDAL"); } /// /// 获取主版面数据访问层类 /// /// 主版面数据访问层 54 烟台大学毕业论文(设计) public static IMotherBoardDAL CreateMotherBoardDAL() { return (IMotherBoardDAL)DependencyProvider.GetDALObject("MotherBoardDAL"); } /// /// 获取子版面数据访问层类 /// /// 子版面数据访问层 public static IBoardDAL CreateBoardDAL() { return (IBoardDAL)DependencyProvider.GetDALObject("BoardDAL"); } /// /// 获取帖子数据访问层类 /// /// 帖子数据访问层 public static IPostDAL CreatePostDAL() { return (IPostDAL)DependencyProvider.GetDALObject("PostDAL"); } } } SQLServer 数据访问辅助类:SQLServerHelper.cs using System; using System.Data; using System.Data.SqlClient; using System.Configuration; namespace NBBS.DALHelper { /// /// 数据访问层辅助类——SQLServer /// 密封类,不可继承 /// 仅包含静态方法,不需实例化直接使用(也可使用 Singleton 模式) /// public sealed class SQLServerHelper { /// 55 烟台大学毕业论文(设计) /// 用于连接 SQLServer 数据库的连接字符串,存于 Web.config 中 /// private static readonly string _sqlConnectionString = ConfigurationManager.AppSettings["SQLServerConnectionString"]; /// /// 执行 SQL 命令,不返回任何值 /// /// SQL 命令 public static void ExecuteSQLNonQurey(string sql) { SqlConnection connection = new SqlConnection(_sqlConnectionString); SqlCommand command = new SqlCommand(sql,connection); connection.Open(); command.ExecuteNonQuery(); connection.Close(); } /// /// 执行 SQL 命令,并返回 SqlDataReader /// /// SQL 命令 /// 包含查询结果的 SqlDataReader public static SqlDataReader ExecuteSQLReader(string sql) { SqlConnection connection = new SqlConnection(_sqlConnectionString); SqlCommand command = new SqlCommand(sql, connection); connection.Open(); SqlDataReader sqlReader = command.ExecuteReader(); //connection.Close(); return sqlReader; } /// /// 执行存储过程,不返回任何值 /// /// 存储过程名 /// 参数 public static void ExecuteProcedureNonQurey(string storedProcedureName,IDataParameter[] parameters) { 56 烟台大学毕业论文(设计) SqlConnection connection = new SqlConnection(_sqlConnectionString); SqlCommand command = new SqlCommand(storedProcedureName,connection); command.CommandType = CommandType.StoredProcedure; if (parameters != null) { foreach (SqlParameter parameter in parameters) { command.Parameters.Add(parameter); } } connection.Open(); command.ExecuteNonQuery(); connection.Close(); } /// /// 执行存储,并返回 SqlDataReader /// /// 存储过程名 /// 参数 /// 包含查询结果的 SqlDataReader public static SqlDataReader ExecuteProcedureReader(string storedProcedureName,IDataParameter[] parameters) { SqlConnection connection = new SqlConnection(_sqlConnectionString); SqlCommand command = new SqlCommand(storedProcedureName,connection); command.CommandType = CommandType.StoredProcedure; if (parameters != null) { foreach (SqlParameter parameter in parameters) { command.Parameters.Add(parameter); } } connection.Open(); SqlDataReader sqlReader = command.ExecuteReader(); //connection.Close(); return sqlReader; } 57 烟台大学毕业论文(设计) /// /// 为可空字符串字段赋值 /// 如果为 null 则赋予 DBNull; /// /// 字段值 /// 参数 public static void SetNullableString(object fieldValue, SqlParameter parameter) { if (fieldValue == null) { parameter.Value = DBNull.Value; } else { parameter.Value = fieldValue; } } /// /// 获得可空字段的值 /// 如果为 DBNull 则返回 null /// /// 字段值 /// 返回值 public static object GetNullableValue(object fieldValue) { if (fieldValue == DBNull.Value) { return null; } else { return fieldValue; } } } } 用户数据访问层的实现(基于 NBear 的 ORM 实现):UserDAL.cs using System; using System.Configuration; using System.Collections; 58 烟台大学毕业论文(设计) using System.Collections.Generic; using System.Data.Common; using NBBS.IDAL; using NBBS.Entity; using NBBS.NBearDAL.EntityConvertor; using NBear.Common; using NBear.Data; namespace NBBS.NBearDAL { /// /// 数据访问层-用户 /// public class UserDAL : IUserDAL { /// /// 插入一个用户 /// /// 用户实体类 /// 操作是否成功 public bool Insert(UserInfo user) { Gateway.SetDefaultDatabase("NBearConnectionString"); t_User userTable = UserConvertor.EntityToNBear(user); DbTransaction transcation = Gateway.Default.BeginTransaction(); try { Gateway.Default.Save(userTable, transcation); transcation.Commit(); return true; } catch { transcation.Rollback(); return false; } finally { Gateway.Default.CloseTransaction(transcation); } } 59 烟台大学毕业论文(设计) /// /// 删除一个用户 /// /// 欲删除用户的 ID public bool Delete(int id) { Gateway.SetDefaultDatabase("NBearConnectionString"); DbTransaction transcation = Gateway.Default.BeginTransaction(); try { Gateway.Default.Delete(transcation, id); transcation.Commit(); return true; } catch { transcation.Rollback(); return false; } finally { Gateway.Default.CloseTransaction(transcation); } } /// /// 更新用户信息 /// /// 用户实体类 public bool Update(UserInfo user) { Gateway.SetDefaultDatabase("NBearConnectionString"); t_User userTable = UserConvertor.EntityToNBear(user); PropertyItem[] properties = { new PropertyItem("User_Age"), new PropertyItem("User_Date"), new PropertyItem("User_Email"), new PropertyItem("User_From"), new PropertyItem("User_Point"), new PropertyItem("User_QQ"), new PropertyItem("User_RealName"), 60 烟台大学毕业论文(设计) new PropertyItem("User_Sex"), new PropertyItem("User_Sign"), new PropertyItem("User_State"), new PropertyItem("User_Status"), new PropertyItem("User_Tel") }; object[] values ={ userTable.User_Age, userTable.User_Date, userTable.User_Email, userTable.User_From, userTable.User_Point, userTable.User_QQ, userTable.User_RealName, userTable.User_Sex, userTable.User_Sign, userTable.User_State, userTable.User_Status, userTable.User_Tel }; PropertyItem[] propertiesLogin = { new PropertyItem("User_Name"), new PropertyItem("User_Password") }; object[] valuesLogin ={ userTable.User_Login.User_Name, userTable.User_Login.User_Password }; DbTransaction transcation = Gateway.Default.BeginTransaction(); try { Gateway.Default.Update(properties, values, t_User._.User_ID == user.ID, transcation); Gateway.Default.Update(propertiesLogin, valuesLogin, t_Login._.User_ID == user.ID, transcation); transcation.Commit(); return true; } catch { transcation.Rollback(); return false; 61 烟台大学毕业论文(设计) } finally { Gateway.Default.CloseTransaction(transcation); } } /// /// 按 ID 取得一个用户的全部信息 /// /// 用户 ID /// 用户实体类 public UserInfo GetByID(int id) { Gateway.SetDefaultDatabase("NBearConnectionString"); t_User userTable = Gateway.Default.Find(t_User._.User_ID == id); return userTable == null ? null : UserConvertor.NBearToEntity(userTable); } /// /// 按身份取得用户信息 /// /// 身份 /// 用户实体集合类 public IList GetByStatus(string status) { IList users = new List(); Gateway.SetDefaultDatabase("NBearConnectionString"); t_User[] userTableList = Gateway.Default.FindArray(t_User._.User_Status == status, t_User._.User_ID.Desc); foreach (t_User userItem in userTableList) { users.Add(UserConvertor.NBearToEntity(userItem)); } return users; } /// 62 烟台大学毕业论文(设计) /// 取得全部记录的数量 /// /// 记录的数量 public int GetCount() { Gateway.SetDefaultDatabase("NBearConnectionString"); return Gateway.Default.Count(null); } /// /// 取得指定身份记录的数量 /// /// 身份 /// 记录的数量 public int GetCountByStatus(string status) { Gateway.SetDefaultDatabase("NBearConnectionString"); return Gateway.Default.Count(t_User._.User_Status == status); } } } 用户简单业务逻辑层的实现:UserBLL.cs using System; using System.Collections; using System.Collections.Generic; using NBBS.Entity; using NBBS.IDAL; using NBBS.IBLL; using NBBS.Factory; namespace NBBS.SimpleBLL { /// /// 业务逻辑类-用户 /// public class UserBLL : IUserBLL { /// /// 注册 /// 63 烟台大学毕业论文(设计) /// 实体类 /// 是否成功 public bool Register(UserInfo user) { user.Date = DateTime.Now; user.Status = "注册会员"; user.State = "正常"; user.Point = 0; user.Password = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfi gFile(user.Password, "MD5"); IUserDAL userDAL = DALFactory.CreateUserDAL(); return userDAL.Insert(user); } /// /// 删除用户 /// /// 欲删除用户的 ID /// 是否成更 public bool Remove(int id) { IUserDAL userDAL = DALFactory.CreateUserDAL(); return userDAL.Delete(id); } /// /// 更新用户信息 /// /// 实体类 /// 是否成功 public bool UpdateInformation(UserInfo user) { IUserDAL userDAL = DALFactory.CreateUserDAL(); return userDAL.Update(user); } /// /// 按 ID 取得某用户信息 /// /// 用户 ID /// 实体类 public UserInfo GetByID(int id) 64 烟台大学毕业论文(设计) { IUserDAL userDAL = DALFactory.CreateUserDAL(); return userDAL.GetByID(id); } /// /// 按用户名取得一个用户的全部信息 /// /// 用户名 /// 用户实体类 public UserInfo GetByName(string name) { ILoginDAL loginDAL = DALFactory.CreateLoginDAL(); LoginInfo login = new LoginInfo(); if ((login = loginDAL.GetByName(name)) == null) { return null; } else { IUserDAL userDAL = DALFactory.CreateUserDAL(); return userDAL.GetByID(login.ID); } } /// /// 用户登录 /// /// 用户名 /// 密码 /// 实体类 public UserInfo Login(string name, string password) { password = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfi gFile(password, "MD5"); ILoginDAL loginDAL = DALFactory.CreateLoginDAL(); LoginInfo login = new LoginInfo(); if ((login = loginDAL.Login(name, password)) == null) { return null; } else 65 烟台大学毕业论文(设计) { IUserDAL userDAL = DALFactory.CreateUserDAL(); return userDAL.GetByID(login.ID); } } /// /// 按身份及分页信息取得用户 /// /// 身份 /// 页号 /// 每页显示多少条信息 /// 用户实体类集合 public IList GetByStatusAndPage(string status,int pageNumber,int itemsPerPage) { IUserDAL userDAL = DALFactory.CreateUserDAL(); IList userList = userDAL.GetByStatus(status); IList userListPage = new List(); int start, end, i; start = (pageNumber - 1) * itemsPerPage; end = start + itemsPerPage - 1; i = start; while (i <= end && i < userList.Count) { userListPage.Add(userList[i]); i++; } return userListPage; } /// /// 按身份取得用户的数量,如果为 null 则取得所有用户数量 /// /// 身份 /// 用户数量 public int GetCount(string status) { IUserDAL userDAL = DALFactory.CreateUserDAL(); if (status == null) { return userDAL.GetCount(); } 66 烟台大学毕业论文(设计) else { return userDAL.GetCountByStatus(status); } } } } 用户异步 WebService 的实现:UserPL.cs using System; using System.Web; using System.Collections; using System.Collections.Generic; using System.Web.Services; using System.Web.Services.Protocols; using System.Text; using System.Web.Script.Services; using System.Web.Security; using NBBS.Entity; using NBBS.Factory; using NBBS.IBLL; /// /// 表示逻辑-用户 /// [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [ScriptService] [GenerateScriptType(typeof(UserInfo))] public class UserPL : System.Web.Services.WebService { /// /// 用户注册的表示逻辑 /// /// 用户实体类 [WebMethod] public void Register(UserInfo user) { Validate validator = new Validate(); if (validator.ValidateUserName(user.Name) != 0 || validator.ValidatePassword(user.Password) == 0 || validator.ValidateEmail(user.Email) != 0 || validator.ValidateCustom(user.Tel, "") != 0 || 67 烟台大学毕业论文(设计) validator.ValidateCustom(user.QQ, "") != 0 || validator.ValidateCustom(user.Age.ToString(), "") != 0) { throw new Exception("必填信息不完整或没有全部通过验证"); } IUserBLL userBLL = BLLFactory.CreateUserBLL(); if (userBLL.Register(user) == false) { throw new Exception("内部错误,请与管理员联系"); } } /// /// 删除用户 /// /// 要删除用户的 ID [WebMethod] public void Remove(int id) { IUserBLL userBLL = BLLFactory.CreateUserBLL(); if (userBLL.Remove(id) == false) { throw new Exception("内部错误,请与管理员联系"); } } /// /// 管理员编辑用户信息 /// /// 用户实体类 [WebMethod] public void AdminEdit(UserInfo user) { IUserBLL userBLL = BLLFactory.CreateUserBLL(); UserInfo newUser = userBLL.GetByID(user.ID); newUser.Status = user.Status; newUser.State = user.State; if (userBLL.UpdateInformation(newUser) == false) { throw new Exception("内部错误,请与管理员联系"); } } 68 烟台大学毕业论文(设计) /// /// 修改密码 /// /// 新密码 /// 新密码确认 [WebMethod(EnableSession=true)] public void ChangePassword(string newPassword,string newPasswordCheck) { Validate validator = new Validate(); if (validator.ValidatePassword(newPassword) == 0) { throw new Exception("密码不符合要求,密码长度必须在 6-16 之间"); } if (newPassword != newPasswordCheck) { throw new Exception("两次输入的密码不一致"); } int userId = ((UserInfo)Session["CurrentUser"]).ID; IUserBLL userBLL = BLLFactory.CreateUserBLL(); UserInfo newUser = userBLL.GetByID(userId); newUser.Password = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfi gFile(newPassword, "MD5"); if (userBLL.UpdateInformation(newUser) == false) { throw new Exception("内部错误,请与管理员联系"); } } /// /// 更新用户信息 /// /// 用户实体类 [WebMethod] public void Update(UserInfo user) { Validate validator = new Validate(); if (validator.ValidateUserName(user.Name) != 0 || validator.ValidatePassword(user.Password) == 0 || 69 烟台大学毕业论文(设计) validator.ValidateEmail(user.Email) != 0 || validator.ValidateCustom(user.Tel, "") != 0 || validator.ValidateCustom(user.QQ, "") != 0 || validator.ValidateCustom(user.Age.ToString(), "") != 0) { throw new Exception("必填信息不完整或没有全部通过验证"); } IUserBLL userBLL = BLLFactory.CreateUserBLL(); if (userBLL.UpdateInformation(user) == false) { throw new Exception("内部错误,请与管理员联系"); } } /// /// 用户登录 /// /// 用户名 /// 密码 /// 验证码 /// 用户实体类 [WebMethod(EnableSession=true)] public UserInfo Login(string name, string password, string safeCode) { if (safeCode != Session["SafeCode"].ToString()) { return new UserInfo(); } else { IUserBLL userBLL = BLLFactory.CreateUserBLL(); UserInfo user = userBLL.Login(name, password); Session["CurrentUser"] = user; return user; } } /// /// 注销登录 /// [WebMethod(EnableSession = true)] public void Logout() { 70 烟台大学毕业论文(设计) Session["CurrentUser"] = null; } /// /// 取得当前登录用户的信息 /// /// 用户实体类 [WebMethod(EnableSession = true)] public UserInfo GetCurrentUserInfo() { return (UserInfo)Session["CurrentUser"]; } /// /// 按身份分页取得用户数据 /// /// 身份 /// 页码 /// 每页多少个条目 /// 用户实体类 [WebMethod] public IList GetByStatusAndPage(string status,int pageNumber, int itemsPerPage) { IUserBLL userBLL = BLLFactory.CreateUserBLL(); return userBLL.GetByStatusAndPage(status, pageNumber, itemsPerPage); } /// /// 取得所有条目的数量 /// /// 所有条目的数量 [WebMethod] public int GetTotal(string status) { IUserBLL userBLL = BLLFactory.CreateUserBLL(); return userBLL.GetCount(status); } } Ajax 用户名验证客户端组件的实现:UserNameValidator.js /// /// FileName : UsernNameValidator.js 71 烟台大学毕业论文(设计) /// Author : 张洋 /// Description : 自定义 ASP.NET Ajax 客户端组件,用于用户名数据格式的 有效性验证(有效条件:由英文字母、数字和汉字组成,且没有重名) /// ClassName : NBBS.CustomControl.UsernNameValidator /// Namespace : NBBS.CustomControl /// Inheritee : Sys.UI.Control /// Type.registerNamespace('NBBS.CustomControl'); NBBS.CustomControl.UserNameValidator = function(associatedElement) { NBBS.CustomControl.UserNameValidator.initializeBase(this, [associatedElement]); } NBBS.CustomControl.UserNameValidator.prototype = { /// /// 私有成员变量 /// /// 待验证数据 /// 是否通过验证 _data: null, _isPass :false, /// /// 属性 /// get_data: function() { return this._data; }, set_data: function(value) { if (this._data !== value) { this._data = value; this.raisePropertyChanged('data'); } }, get_isPass: function() { return this._isPass; }, set_isPass: function(value) { if (this._isPass !== value) { 72 烟台大学毕业论文(设计) this._isPass = value; this.raisePropertyChanged('isPass'); } }, /// /// 构造函数 /// initialize: function() { NBBS.CustomControl.UserNameValidator.callBaseMethod(this, 'initialize'); this.get_element().innerHTML=" 用 户名 可以由英文字母、数字和汉字组成"; this.set_isPass(false); }, /// /// 析构函数 /// dispose: function() { if(this.get_element()) { this.get_element().innerHTML=""; } NBBS.CustomControl.UserNameValidator.callBaseMethod(this, 'dispose'); }, /// /// 验证数据 /// validate: function() { this.get_element().innerHTML="\"\" 正在验证用户名……"; Validate.ValidateUserName(this.get_data(),this.onSucceeded,this.onFai led,this); }, /// /// 验证正常进行时的回调函数 73 烟台大学毕业论文(设计) /// onSucceeded: function(result,validator) { if(result==0) { validator.get_element().innerHTML="\"\""; validator.set_isPass(true); } else if(result==1) { validator.get_element().innerHTML="用户名不合法,您的用户名仅能由英文字母、数字和汉字组 成"; validator.set_isPass(false); } else { validator.get_element().innerHTML="此用户名已经被使用,请更换"; validator.set_isPass(false); } }, /// /// 验证出现异常,无法进行时的回调 /// onFailed: function(error,validator) { validator.get_element().innerHTML="内 部错误,无法完成检验,请与管理员联系"; } } NBBS.CustomControl.UserNameValidator.registerClass('NBBS.CustomContro l.UserNameValidator', Sys.UI.Control); if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded(); 用户注册异步控制器的实现:Register.aspx.js /// /// FileName : Register.aspx.js /// Author : 张洋 /// Description : Register.aspx 页面的后台 Ajax 脚本 /// 74 烟台大学毕业论文(设计) /// /// 将各种控件生命为全局变量,方便调用 /// var tbUserName; var tbPassword; var tbPasswordCheck; var tbEmail; var tbRealName; var tbFrom; var tbTel; var tbQQ; var sltSex; var tbAge; var btnSubmit; var btnCancel; var vUserName; var vPassword; var vPasswordCheck; var vEmail; var vTel; var vQQ; var vAge; var mask; var result; var infoText; Sys.Application.add_init(onPageInit); function onPageInit() { /// /// 图片预加载 /// var imgInfoBg = new Image(); var imgLoadingValidate = new Image(); var imgLoadingProcess = new Image(); imgInfoBg.src = "Images/bg_info.gif"; imgLoadingValidate.src = "Icons/loading_validate.gif"; imgLoadingProcess.src = "Icons/loading_process.gif"; /// /// 创建客户端控件 /// tbUserName=$create(Sys.Preview.UI.TextBox,null,null,null,$get("Name") 75 烟台大学毕业论文(设计) ); tbPassword=$create(Sys.Preview.UI.TextBox,null,null,null,$get("Passwo rd")); tbPasswordCheck=$create(Sys.Preview.UI.TextBox,null,null,null,$get("P asswordCheck")); tbEmail=$create(Sys.Preview.UI.TextBox,null,null,null,$get("Email")); tbRealName=$create(Sys.Preview.UI.TextBox,null,null,null,$get("RealNa me")); tbFrom=$create(Sys.Preview.UI.TextBox,null,null,null,$get("From")); tbTel=$create(Sys.Preview.UI.TextBox,null,null,null,$get("Tel")); tbQQ=$create(Sys.Preview.UI.TextBox,null,null,null,$get("QQ")); sltSex=$create(Sys.Preview.UI.Selector,null,null,null,$get("Sex")); tbAge=$create(Sys.Preview.UI.TextBox,null,null,null,$get("Age")); btnSubmit=$create(Sys.Preview.UI.Button,null,null,null,$get("Submit") ); btnCancel=$create(Sys.Preview.UI.Button,null,null,null,$get("Cancel") ); vUserName=$create(NBBS.CustomControl.UserNameValidator,null,null,null ,$get("NameValidator")); vPassword=$create(NBBS.CustomControl.PasswordValidator,null,null,null ,$get("PasswordValidator")); vPasswordCheck=$create(NBBS.CustomControl.PasswordCheckValidator,null ,null,null,$get("PasswordCheckValidator")); vEmail=$create(NBBS.CustomControl.EmailValidator,null,null,null,$get( "EmailValidator")); vTel=$create(NBBS.CustomControl.CustomValidator,{tip: "请输入手机 或座机号码",errorMessage: "电话号码格式不正确",allowEmpty: true,textBox: tbTel,regularExpression: ""},null,null,$get("TelValidator")); vQQ=$create(NBBS.CustomControl.CustomValidator,{tip: "请输入您的 QQ 号 ",errorMessage: "QQ 号码格式不合法",allowEmpty: true,textBox: tbQQ,regularExpression: "^\\d{5,}$"},null,null,$get("QQValidator")); 76 烟台大学毕业论文(设计) vAge=$create(NBBS.CustomControl.CustomValidator,{tip: "请输入您的 年龄",errorMessage: "年龄不合法,年龄必须是1-200 之间的数字",allowEmpty: true,textBox: tbAge,regularExpression: ""},null,null,$get("AgeValidator")); mask=$create(NBBS.CustomControl.MaskWindow,{windowElement: $get("info"),windowWidth: 400,windowHeight: 150},null,null,$get("mask")); result=$create(NBBS.CustomControl.InformationPanel,{loadingIcon: "Icons/loading_process.gif"},null,null,$get("result")); //$create(NBBS.CustomBehavior.DragBehavior,{dragElement: $get("info")},null,null,$get("infoWindowHead")); infoText = $create(Sys.Preview.UI.Label,null,null,null,$get("infoText")); /// /// 创建事件监听 /// $addHandler($get("Name"),"blur",tbUserName_onBlur); $addHandler($get("Password"),"blur",tbPassword_onBlur); $addHandler($get("PasswordCheck"),"blur",tbPasswordCheck_onBlur); $addHandler($get("Email"),"blur",tbEmail_onBlur); $addHandler($get("Tel"),"blur",tbTel_onBlur); $addHandler($get("QQ"),"blur",tbQQ_onBlur); $addHandler($get("Age"),"blur",tbAge_onBlur); btnSubmit.add_click(btnSubmit_onClick); btnCancel.add_click(btnCancel_onClick); } /// /// tbUserName 失去焦点处理函数 /// 数据验证 /// function tbUserName_onBlur() { vUserName.set_data(tbUserName.get_text()); vUserName.validate(); } /// /// tbPassword 失去焦点处理函数 /// 数据验证 /// function tbPassword_onBlur() { vPassword.set_data(tbPassword.get_text()); vPassword.validate(); 77 烟台大学毕业论文(设计) vPasswordCheck.initialize(); tbPasswordCheck.set_text(""); } /// /// tbPasswordCheck 失去焦点处理函数 /// 数据验证 /// function tbPasswordCheck_onBlur() { vPasswordCheck.set_data(tbPasswordCheck.get_text()); vPasswordCheck.set_checkData(tbPassword.get_text()); vPasswordCheck.validate(); } /// /// tbEmail 失去焦点处理函数 /// 数据验证 /// function tbEmail_onBlur() { vEmail.set_data(tbEmail.get_text()); vEmail.validate(); } /// /// tbTel 失去焦点处理函数 /// 数据验证 /// function tbTel_onBlur() { vTel.set_data(tbTel.get_text()); vTel.validate(); } /// /// tbQQ 失去焦点处理函数 /// 数据验证 /// function tbQQ_onBlur() { vQQ.set_data(tbQQ.get_text()); vQQ.validate(); } /// /// tbAge 失去焦点处理函数 78 烟台大学毕业论文(设计) /// 数据验证 /// function tbAge_onBlur() { vAge.set_data(tbAge.get_text()); vAge.validate(); } /// /// btnSubmit 单击处理函数 /// 提交注册 /// function btnSubmit_onClick() { result.showProcess(); btnSubmit.get_element().disabled="true"; btnCancel.get_element().disabled="true"; if(vUserName.get_isPass() === true && vPassword.get_isPass() === true && vPasswordCheck.get_isPass() === true && vEmail.get_isPass() === true && vTel.get_isPass() === true && vQQ.get_isPass() === true && vAge.get_isPass() === true) { var user = new NBBS.Entity.UserInfo(); user.Name = tbUserName.get_text(); user.Password = tbPassword.get_text(); user.Email = tbEmail.get_text(); user.RealName = tbRealName.get_text(); user.From = tbFrom.get_text(); user.Tel = tbTel.get_text(); user.QQ = tbQQ.get_text(); user.Sex = sltSex.get_selectedValue(); user.Age = tbAge.get_text(); UserPL.Register(user,onRegisterSucceeded,onRegisterFailed); } else { result.set_errorMessage("您输入的信息不完整,或者没有全部通过 验证,请检查"); result.showError(); } 79 烟台大学毕业论文(设计) btnSubmit.get_element().disabled=""; btnCancel.get_element().disabled=""; } /// /// btnCancel 单击处理函数 /// 重置表单 /// function btnCancel_onClick() { reset(); } /// /// 注册操作完成后的回调函数 /// function onRegisterSucceeded(response) { sltSex.get_element().disabled="true"; result.hide(); reset(); infoText.set_text("恭喜!注册成功!"); mask.show(); } /// /// 注册操作的异常处理 /// function onRegisterFailed(error) { result.set_errorMessage(error.get_message()); result.showError(); } /// /// 表单重置 /// function reset() { tbUserName.set_text(""); tbPassword.set_text(""); tbPasswordCheck.set_text(""); tbEmail.set_text(""); tbRealName.set_text(""); tbFrom.set_text(""); tbTel.set_text(""); tbQQ.set_text(""); sltSex.set_selectedValue("保密"); 80 烟台大学毕业论文(设计) tbAge.set_text(""); vUserName.initialize(); vPassword.initialize(); vPasswordCheck.initialize(); vEmail.initialize(); vTel.initialize(); vQQ.initialize(); vAge.initialize(); } /// /// 关闭模态窗体 /// function close() { sltSex.get_element().disabled=""; mask.hide(); } if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded(); 81 烟台大学毕业论文(设计) 附录二:Demo 示例的工程结构介绍 附图 2.1 Demo 工程结构图 Web:表示层。 DALHelper:数据访问层辅助类。 Entity:实体类。 Factory:依赖注入容器及工厂类。 IBLL:业务逻辑层接口。 IDAL:数据访问层接口。 NBearDAL:基于 NBear 框架的 ORM 数据访问层实现。 NbearEntityDesign:NBear 设计工程。 NunitTest:NUnit 的单元测试工程。 SimpleBLL:业务逻辑层的简单实现。 Tools:其他辅助类。 82
还剩81页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

lwq2006

贡献于2012-07-15

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf