基于.NET平台的分层架构实战

Page 1

基于.NET 平台的分层架构实战 二〇〇九年三月 果果红工作室 来源:cnblogs 作者:佚名 整理:果果红


一、 综述 ................................................................................................................................... 1 二、 需求分析与数据库设计 ................................................................................................... 2 需求分析: ............................................................................................................................... 2 数据库设计: ........................................................................................................................... 3 三、 架构概要设计 ................................................................................................................... 3 架构基本原则: ....................................................................................................................... 3 逐层调用原则及单向调用原则 ....................................................................................... 3 针对接口编程,而不是针对实现编程 ........................................................................... 4 依赖倒置原则 ................................................................................................................... 4 封装变化原则 ................................................................................................................... 5 开放-关闭原则 ................................................................................................................. 5 单一归属原则 ................................................................................................................... 5 层次划分: ............................................................................................................................... 5 职责划分: ............................................................................................................................... 5 模块划分及交互设计: ........................................................................................................... 5 四、 实体类的设计与实现 ....................................................................................................... 6 AdminInfo.cs:........................................................................................................................ 7 MessageInfo.cs: .................................................................................................................... 8 CommentInfo.cs: ................................................................................................................. 9 五、 接口的设计与实现 ......................................................................................................... 10 由需求分析,列出主要UI .................................................................................................... 11 由UI识别业务逻辑操作 ........................................................................................................ 11 经过整理,可得以下接口操作: ......................................................................................... 12 IAdminBLL.cs: .................................................................................................................... 12 IMessageBLL.cs: ................................................................................................................ 13 ICommentBLL.cs ............................................................................................................... 14 由业务逻辑确定数据访问操作 ............................................................................................. 15 IAdminDAL.cs: ................................................................................................................. 15 IMessageDAL.cs: ............................................................................................................. 17 ICommentDAL.cs: ........................................................................................................... 18 六、 依赖注入机制及IoC的设计与实现 .............................................................................. 19 配置......................................................................................................................................... 20 实现缓存操作辅助类 ............................................................................................................. 20 CacheAccess.cs: .......................................................................................................... 20 封装依赖注入代码 ................................................................................................................. 21 DependencyInjector.cs: ............................................................................................ 21 实现工厂 ................................................................................................................................. 23 DALFactory.cs .............................................................................................................. 23 BLLFactory.cs ............................................................................................................... 24


基于.NET 平台的分层架构实战

果果红工作室

七、 数据访问层的第一种实现:Access+动态生成SQL语言 ............................................ 25 第一步,我们要将Access数据库搭建完成,具体做法如下。 ........................................ 25 第二步,我们要进行一些配置。 ......................................................................................... 25 第三步,新建一个工程。 ..................................................................................................... 26 1.编写数据访问助手类 ................................................................................................. 26 AccessDALHelper.cs: ................................................................................................. 26 2.实现具体的数据访问操作类 ..................................................................................... 28 AdminDAL: ................................................................................................................... 28 八、 对数据访问层第一种实现 ............................................................................................. 32 首先是IDataReaderToEntityStrategy接口 .................................................................... 33 IDataReaderToEntityStrategy.cs: ............................................................................ 33 然后以Admin为例,看一下策略的具体实现: ................................................................ 34 AdminDataReaderToEntityStrategy.cs: ................................................................ 34 下一个,是重构后的AccessDALHelper,增加了三个方法。 ....................................... 35 AccessDALHelper.cs: ................................................................................................. 35 最后以Admin为例,看一下简化后的数据访问层实现: ................................................ 39 AdminDAL.cs: .............................................................................................................. 39 九、 数据访问层的第二种实现:SQLServer+存储过程 .................................................. 41 创建新工程 ............................................................................................................................. 42 创建数据库 ............................................................................................................................. 42 编写存储过程 ......................................................................................................................... 42 PROCEDURE [dbo].[Pr_InsertAdmin] ..................................................................... 42 PROCEDURE [dbo].[Pr_DeleteAdmin] ................................................................... 43 PROCEDURE [dbo].[Pr_UpdateAdmin] ................................................................. 43 PROCEDURE [dbo].[Pr_GetAdminByID] ................................................................ 44 PROCEDURE

[dbo].[Pr_GetAdminByNameAndPassword] ............................ 44

PROCEDURE [dbo].[Pr_GetAdminByName] ......................................................... 44 PROCEDURE [dbo].[Pr_GetAllAdmin] .................................................................... 45 编写数据库辅助类 ................................................................................................................. 45 SQLServerDALHelper.cs: ........................................................................................... 45 实现数据访问层 ..................................................................................................................... 47 AdminDAL.cs: .............................................................................................................. 47 十、 数据访问层的第三种实现:基于NBear框架 ............................................................. 51 1.创建实体设计工程 ............................................................................................................. 52 EntityDesign.cs ............................................................................................................ 52 2.创建NBear专用实体类及配置文件 ................................................................................. 53 3.编写转换器 ......................................................................................................................... 54 AdminConvertor.cs .................................................................................................... 54 4.实现数据访问层 ................................................................................................................. 55 AdminDAL.cs ............................................................................................................... 55 十一、

业务逻辑层的实现 ................................................................................................. 58

3


基于.NET 平台的分层架构实战

果果红工作室

1.建立工程 ............................................................................................................................. 58 2.配置依赖注入 ..................................................................................................................... 58 3.编写散列加密工具类 ......................................................................................................... 59 Encryptor.cs ................................................................................................................. 59 4.实现业务逻辑层 ................................................................................................................. 59 AdminBLL.cs ................................................................................................................. 60 十二、 表示层的实现 ......................................................................................................... 61 1.设计界面 ............................................................................................................................. 62 Login.aspx: ................................................................................................................... 62 Common.css ................................................................................................................ 64 Login.aspx.css .............................................................................................................. 64 2.表示逻辑 ............................................................................................................................. 65 Login.aspx.cs: ............................................................................................................... 65

4


基于.NET 平台的分层架构实战

果果红工作室

基于.NET 平台的分层架构实战 一、 综述 通过浏览博客园的文章发现,很多朋友对分层架构特别感兴趣,刚好我刚做完的毕业设 计就是专门研究.NET 平台上分层架构的(题目叫“基于.NET 平台的分层架构与设计模式应 用研究”)。通过做这篇论文,我对分层架构有了一定的了解,所以,就萌发了想写一个文 章系列,详述一下分层架构。然而,论文的理论性太强,不适合在网上发布,尤其不适合初 学者理解,所以,我想在这个文章系列中,少讲理论,而是通过做一个完整的案例来讨论分 层架构的基本方法,这样会直观很多。希望在这个文章系列的写作过程中,能和朋友们一起 学习,一起进步。 为了让朋友们把主要精力放在理解分层架构而不是案例本身,我准备选择一个相对简单 的留言本系统作为 Demo,这个系统的名字就叫做 NGuestBook。 初步计划将这个文章系列分为以下几篇: 1.综述 2.系统需求分析及数据库设计 3.架构概要设计 4.实体类的实现 5.接口的设计与实现 6.依赖注入及 IoC 的设计与实现 7.数据访问层的第一种实现——Access+动态生成 SQL 语言 8.数据访问层的第二种实现——SQLServer+存储过程 9.数据访问层的第三种实现——基于 NBear 框架的 ORM 实现 10.业务逻辑层的实现 11.表示层的实现 12.使用 ASP.NET AJAX 框架对表示层进行改进 13.总结 当然,以上只是初步计划,在写文章的过程中可能会根据具体情况适当调整,但是内容 大体就是这些。 这个文章系列不会对所用到的技术进行详细讲解,具体请参考相关文献,阅读文章前最 好能对以下技术有一个了解: 1.C#语言 2.ASP.NET 3.设计模式 4.关系数据库基础知识 5.软件架构基本原则与软件工程基础知识

1


基于.NET 平台的分层架构实战

果果红工作室

6.基于 NBear 框架的 ORM 技术 7.JavaScript,Ajax 8.ASP.NET AJAX 框架(特别是客户端编程) 9.HTML,CSS,标准化布局 另外,本文章系列是基于.NET framework2.0 框架平台进行讨论,3.5 平台的新特性(如 LINQ、ASP.NET MVC 等)不会讨论,IDE 使用 Visual Studio 2005,数据库会用到 SQLServer2005 Express 和 Access2003

二、 需求分析与数据库设计 在实际的项目中,需求分析和数据库的设计是很重要的一个环节,这个环节会直接影响 项目的开发过程和质量。实际中,这个环节不但需要系统分析师、软件工程师等计算机方面 的专家,还需要相关领域的领域专家参与才能完成。 但是,在这个文章系列中,所要使用的 Demo 仅仅是一个例子,而且其业务极为简单, 因此,这里并不是真正的需求分析和数据库设计,而是将 Demo 的需求和数据库罗列至此, 使朋友们对 Demo 有一个大体的了解,方便后续文章中开发过程的理解。

需求分析: 这个项目是一个留言本,其业务极为简单,现将其描述如下。 1.任何访问者可以进行留言,留言完成后,不会立即显示正文,而是要经过管理员验证 后才可显示。 2.任何访问者可以对留言发表评论,未通过验证的留言不可以评论。 3.管理员可以对留言进行回复(这个回复不同于评论,是直接显示在正文下面,而且是 一个留言只能有一个回复),并可对留言与评论实行删除,以及对留言进行通过验证操作。 4.管理员分为超级管理员和普通管理员。超级管理员只有一个,负责对普通管理员实行 添加、删除操作。普通管理员可偶多个,负责对留言的管理,并可以修改自己的登录密码。 这个项目的用例图如下:

2


基于.NET 平台的分层架构实战

果果红工作室

数据库设计: 设计数据表之前,首先进行实体和关系的识别与确定。 通过需求分析,可以观察得出,本项目的实体有:管理员(不包括超级管理员) ,留言, 评论。本项目的关系有:留言与评论间的一对多关系。 进一步,数据库各表的设计如下: 管理员表(TAdmin) ID

int

管理员 ID

Name

NotNull

varchar(20)

Password

登录名

varchar(50)

主键,自增 NotNull

登录密码

NotNull

使用 MD5 加密

留言表(TMessage) ID

int

留言 ID

NotNull

主键,自增

GuestName

varchar(20)

留言者用户名

GuestEmail

varchar(100)

留言者 E-mail

Content

text

留言内容

Time

datetime

Reply

text

IsPass

varchar(10)

Null

NotNull

发表留言时间

回复

NotNull

NotNull

Null 是否通过验证

NotNull

评论表(TComment) ID

int

评论 ID

Content Time

text

datetime

MessageID

int

NotNull 评论内容

主键,自增 NotNull

发表评论时间

NotNull

所属留言的 ID

外键

三、 架构概要设计 本文主要是对将要实现的架构进行一个总体的描述,使朋友们对这个架构有个宏观上的 认识。这篇文章理论性的东西会偏多一点,从下篇开始,将进行实际项目的开发。这篇文章 的许多内容摘自我的毕业论文。

架构基本原则: 这里,将描述一些在这个架构设计中的基本原则,其中很多都是经典的设计原则,不过 针对分层架构的特点,用我自己的语言进行了描述。其中也有我自己提出的原则。

逐层调用原则及单向调用原则 现在约定将 N 层架构的各层依次编号为 1、2、…、K、…、N-1、N,其中层的编号越 大,则越处在上层。那么,我们设计的架构应该满足以下两个原则:

3


基于.NET 平台的分层架构实战

果果红工作室

1.第 K(1<K<=N)层只准依赖第 K-1 层,而不可依赖其他底层。 2.如果 P 层依赖 Q 层,则 P 的编号一定大于 Q。 其中第一个原则,保证了依赖的逐层性,及整个架构的依赖是逐层向下的,而不能跨层 依赖。第二个原则,则保证了依赖的单向性,及只能上层依赖底层,而不能底层反过来依赖 上层。

针对接口编程,而不是针对实现编程 这里所指的接口,不是特指编程语言中的具体语言元素(如 C#中由 Interface 定义的 语言接口),而是指一种抽象的,在语义层面上起着接合作用语义体。它的具体实现,可能 是接口,可能是抽象类,甚至可能是具体类。 我认为,从不同的视角,接口可以有以下两种定义: 1.接口是一组规则的集合,它规定了实现本接口的类或接口必须拥有的一组规则。体现 了自然界“如果你是……则必须能……”的理念。 2.接口是在一定粒度视图上同类事物的抽象表示。注意这里我强调了在一定粒度视图上, 因为“同类事物”这个概念是相对的,它因为粒度视图不同而不同。 具体到 N 层架构中,针对接口编程的意义在部分上是这样的: 现仍约定将 N 层架构的各层依次编号为 1、2、…、K、…、N-1、N,其中层的编号越 大,则越处在上层,那么第 K 层不应该依赖具体一个 K-1 层,而应该依赖一个 K-1 层的接 口,即在第 K 层中不应该有 K-1 层中的某个具体类。

依赖倒置原则 在软件设计原则中,有一种重要的思想叫做依赖倒置。它的核心思想是:不能让高层组 件依赖底层组件,而且,不管高层组件和底层组件,两者都应依赖于抽象。 那么,这个原则和我们上面的原则是否矛盾呢?其实并不矛盾。 因为这个原则定义中的“依赖”是指“具体依赖”,而上面定义中的依赖全部指“抽象 依赖”。我对这两种依赖的定义如下: 具体依赖——如果 P 层中有一个或一个以上的地方实例化了 Q 层中某个具体类,则说 P 层具体依赖于 Q 层。 抽象依赖——如果 P 层没有实例化 Q 层中的具体类,而是在一个或一个以上的地方实 例化了 Q 层中某个接口,则说 P 层抽象依赖于 Q 层,也叫接口依赖于 Q 层。 从这两个定义可以看到,所谓的依赖倒置原则,正是上面提到针对接口编程,而不是针 对实现编程,两者在本质上是统一的。 综上所述,可以看出,本课题设计的分层架构,应该是这样一种架构: 1.N 层架构的各层依次编号为 1、2、…、K、…、N-1、N,其中层的编号越大,则越 处在上层。 2.架构中仅存在一种依赖,即第 K 层接口依赖第 K-1 层,其中 1<K<=N。

4


基于.NET 平台的分层架构实战

果果红工作室

封装变化原则 封装变化的原则定义为:找出应用中可能需要变化之处,把它们独立出来,不要和那些 不需要变化的代码混杂在一起。

开放-关闭原则 开发-关闭原则定义为:对扩展开放,对修改关闭。 具体到 N 层架构中,可以描述为:当某一层有了一个新的具体实现时,它应该可以在 不修改其他层的情况下,与此新实现无缝连接,顺利交互。

单一归属原则 在这个架构中,任何一个操作类都应该有单一的职责,属于单独的一层,而不能同时担 负两种职责或属于多个层次(实体类及辅助类可以被多个层使用,但它们不属于任何一个层, 而是独立存在)。

层次划分: 目前,典型的分层架构是三层架构,即自底向上依次是数据访问层、业务逻辑层和表示 层。 这种经典架构经历了时间的考验和实践的多次检验,被认为是合理、有效的分层设计, 所以,在本文中,将沿袭这种经典架构,使用数据访问层、业务逻辑层和表示层的三层架构 体系。

职责划分: 目前,在典型的三层架构中,对层次各自的职责划分并没有一个统一的规范,综合现有 的成功实践和.NET 平台的特殊性,在本文中将三层架构的职责划分如下: 数据访问层——负责与数据源的交互,即数据的插入、删除、修改以及从数据库中读 出数据等操作。对数据的正确性和有效性不负责,对数据的用途不了解,不负担任何业务逻 辑。 业务逻辑层——负责系统领域业务的处理,负责逻辑性数据的生成、处理及转换。对 流入的逻辑性数据的正确性及有效性负责,对流出的逻辑性数据及用户性数据不负责,对数 据的呈现样式不负责。 表示层——负责接收用户的输入、将输出呈现给用户以及访问安全性验证。对流入的 数据的正确性和有效性负责,对呈现样式负责,对流出的数据正确性不负责,但负责在数据 不正确时给出相应的异常信息。

模块划分及交互设计: 综合以上分析,可在宏观上将整个系统分为一下几个模块: 实体类模块——一组实体类的集合,负责整个系统中数据的封装及传递。

5


基于.NET 平台的分层架构实战

果果红工作室

数据访问层接口族——一组接口的集合,表示数据访问层的接口。 业务逻辑层接口族——一组接口的集合,表示业务逻辑层的接口。 数据访问层模块——一组类的集合,完成数据访问层的具体功能,实现数据访问层接 口族。 业务逻辑层模块——一组类的集合,完成业务逻辑层的具体功能,实现业务逻辑层接 口族。 表示层模块——程序及可视元素的集合,负责完成表示层的具体功能。 IoC 容器模块——负责依赖注入的实现。 辅助类模块——完成全局辅助性功能。 各模块见交互关系如下:

这篇中理论比较多,但是它是整个架构的基础,可以帮助朋友们对将要实现的项目架构 及要遵循的原则有一个整体的了解。当然,在后续文章中,将主要讨论 Demo 项目的实际 开发过程,那时,这些思想和理论性的东西将得到体现。

四、 实体类的设计与实现

6


基于.NET 平台的分层架构实战

果果红工作室

实体类是现实实体在计算机中的表示。它贯穿于整个架构,负担着在各层次及模块间传 递数据的职责。一般来说,实体类可以分为“贫血实体类”和“充血实体类”,前者仅仅保 存实体的属性,而后者还包含一些实体间的关系与逻辑。我们在这个 Demo 中用的实体类 将是“贫血实体类”。 大多情况下,实体类和数据库中的表(这里指实体表,不包括表示多对多对应的关系表) 是一一对应的,但这并不是一个限制,在复杂的数据库设计中,有可能出现一个实体类对应 多个表,或者交叉对应的情况。在本文的 Demo 中,实体类和表是一一对应的,并且实体 类中的属性和表中的字段也是对应的。 在看实体类的代码前,先看一下系统的工程结构。

如上图所示,在初始阶段,整个系统包括 6 个工程,它们的职责是这样的: Web——表示层 Entity——存放实体类 Factory——存放和依赖注入及 IoC 相关的类 IBLL——存放业务逻辑层接口族 IDAL——存放数据访问层接口族 Utility——存放各种工具类及辅助类 这只是一个初期架构,主要是将整个系统搭一个框架,在后续开发中,将会有其他工程 被陆陆续续添加进来。 我 们 的 实 体 类 将 放 在 Entity 工 程 下 , 这 里 包 括 三 个 文 件 : AdminInfo.cs , MessageInfo.cs,CommentInfo.cs,分别是管理员实体类、留言实体类和评论实体类。 具体代码如下:

AdminInfo.cs: using System; namespace NGuestBook.Entity { /// <summary> /// 实体类-管理员 /// </summary> [Serializable] public class AdminInfo {

7


基于.NET 平台的分层架构实战

果果红工作室

private int id; private string name; private string password; public int ID { get { return this.id; } set { this.id = value; } } public string Name { get { return this.name; } set { this.name = value; } } public string Password { get { return this.password; } set { this.password = value; } } } }

MessageInfo.cs: using System; namespace NGuestBook.Entity { /// <summary> /// 实体类-留言 /// </summary> [Serializable] public class MessageInfo { private int id; private string guestName; private string guestEmail; private string content; private DateTime time; private string reply; private string isPass; public int ID { get { return this.id; } set { this.id = value; }

8


基于.NET 平台的分层架构实战

果果红工作室

} public string GuestName { get { return this.guestName; } set { this.guestName = value; } } public string GuestEmail { get { return this.guestEmail; } set { this.guestEmail = value; } } public string Content { get { return this.content; } set { this.content = value; } } public DateTime Time { get { return this.time; } set { this.time = value; } } public string Reply { get { return this.reply; } set { this.reply = value; } } public string IsPass { get { return this.isPass; } set { this.isPass = value; } } } }

CommentInfo.cs: using System; namespace NGuestBook.Entity

9


基于.NET 平台的分层架构实战

果果红工作室

{ /// <summary> /// 实体类-评论 /// </summary> [Serializable] public class CommentInfo { private int id; private string content; private DateTime time; private int message; public int ID { get { return this.id; } set { this.id = value; } } public string Content { get { return this.content; } set { this.content = value; } } public DateTime Time { get { return this.time; } set { this.time = value; } } public int Message { get { return this.message; } set { this.message = value; } } } }

大家可以看出,实体类的代码很简单,仅仅是负责实体的表示和数据的传递,不包含任 何逻辑性内容。下篇将介绍接口的设计。

五、 接口的设计与实现

10


基于.NET 平台的分层架构实战

果果红工作室

接下来,将进行接口的设计。这里包括数据访问层接口和业务逻辑层接口。在分层架构 中,接口扮演着非常重要的角色,它不但直接决定了各层中的各个操作类需要实现何种操作, 而且它明确了各个层次的职责。接口也是系统实现依赖注入机制不可缺少的部分。 本项目的接口设计将按如下顺序进行: 1.首先由前文的需求分析,列出主要的 UI 部分。 2.分析各个 UI 需要什么业务逻辑支持,从而确定业务逻辑层接口。 3.分析业务逻辑层接口需要何种数据访问操作,从而确定数据访问层接口。 另外,为保证完全的面向对象特性,接口之间的数据传递主要靠实体类或实体类集合, 禁止使用 DataTable 等对象传递数据。

由需求分析,列出主要 UI 需求分析部分,请参看 基于.NET平台的分层架构实战(二)——需求分析与数据库设 计 。有需求分析,可以列出系统中主要应包括以下UI: UI01——主页面,列出全部的留言及相应评论,支持分页显示。留言按发表时间逆序 显示,评论紧跟在相应留言下。管理员可以通过相应链接对留言执行通过验证、删除、回复 以及对评论进行删除操作。游客可通过相应连接进入发表留言评论页面。 UI02——发表留言页面,供游客发表新留言。 UI03——发表评论页面,供游客发表评论。 UI04——回复留言页面,供管理员回复留言。 UI05——管理员登录页面。 UI06——管理员修改个人密码的页面。 UI07——超级管理员登录后的页面,主要提供管理员列表。可以通过相应链接将指定 管理员删除。 UI08——添加新管理员的页面。 UI09——操作成功完成后的跳转提示页面。 UI10——系统出现异常时显示友好出错信息的页面。

由 UI 识别业务逻辑操作 UI01:按分页取得留言,按指定留言取得全部评论,将指定留言通过验证,将指定留 言删除,将指定评论删除 UI02:添加新留言 UI03:添加新评论 UI04:回复留言 UI05:管理员登录 UI06:修改管理员密码 UI07:取得全部管理员信息,删除管理员 UI08:添加新管理员

11


基于.NET 平台的分层架构实战

果果红工作室

经过整理,可得以下接口操作: IAdminBLL: Add(添加管理员),Remove(删除管理员),ChangePassword(修改管理员密码), Login(管理员登录),GetAll(取得全部管理员) IMessageBLL: Add(添加留言),Remove(删除留言),Revert(回复留言) ,Pass(将留言通过验 证),GetByPage(按分页取得留言) ICommentBLL: Add(添加评论),Remove(删除评论),GetByMessage(按留言取得全部评论) 这三个接口文件都放在 IBLL 工程下,具体代码如下:

IAdminBLL.cs: using using using using

System; System.Collections.Generic; System.Text; NGuestBook.Entity;

namespace NGuestBook.IBLL { /// <summary> /// 业务逻辑层接口-管理员 /// </summary> public interface IAdminBLL { /// <summary> /// 添加管理员 /// </summary> /// <param name="admin">新管理员实体类</param> /// <returns>是否成功</returns> bool Add(AdminInfo admin); /// <summary> /// 删除管理员 /// </summary> /// <param name="id">欲删除的管理员的ID</param> /// <returns>是否成功</returns> bool Remove(int id); /// <summary> /// 修改管理员密码 /// </summary> /// <param name="id">欲修改密码的管理员的ID</param> /// <param name="password">新密码</param> /// <returns>是否成功</returns> bool ChangePassword(int id, string password);

12


基于.NET 平台的分层架构实战

果果红工作室

/// <summary> /// 管理员登录 /// </summary> /// <param name="name">管理员登录名</param> /// <param name="password">管理员密码</param> /// <returns>如果登录成功,则返回相应管理员的实体类,否则返回null</returns> AdminInfo Login(string name, string password); /// <summary> /// 取得全部管理员信息 /// </summary> /// <returns>管理员实体类集合</returns> IList<AdminInfo> GetAll(); } }

IMessageBLL.cs: using System; using System.Collections.Generic; using System.Text; using NGuestBook.Entity; namespace NGuestBook.IBLL { /// <summary> /// 业务逻辑层接口-留言 /// </summary> public interface IMessageBLL { /// <summary> /// 添加留言 /// </summary> /// <param name="message">新留言实体类</param> /// <returns>是否成功</returns> bool Add(MessageInfo message); /// <summary> /// 删除留言 /// </summary> /// <param name="id">欲删除的留言的ID</param> /// <returns>是否成功</returns> bool Remove(int id); /// <summary> /// 回复留言 /// </summary>

13


基于.NET 平台的分层架构实战

果果红工作室

/// <param name="id">要回复的留言的ID</param> /// <param name="reply">回复信息</param> /// <returns>是否成功</returns> bool Revert(int id, string reply); /// <summary> /// 将留言通过验证 /// </summary> /// <param name="id">通过验证的留言的ID</param> /// <returns>是否成功</returns> bool Pass(int id); /// <summary> /// 按分页取得留言信息 /// </summary> /// <param name="pageSize">每页显示几条留言</param> /// <param name="pageNumber">当前页码</param> /// <returns>留言实体类集合</returns> IList<MessageInfo> GetByPage(int pageSize, int pageNumber); } }

ICommentBLL.cs using System; using System.Collections.Generic; using System.Text; using NGuestBook.Entity; namespace NGuestBook.IBLL { /// <summary> /// 业务逻辑层接口-评论 /// </summary> public interface ICommentBLL { /// <summary> /// 添加评论 /// </summary> /// <param name="comment">新评论实体类</param> /// <returns>是否成功</returns> bool Add(CommentInfo comment); /// <summary> /// 删除评论

14


基于.NET 平台的分层架构实战

果果红工作室

/// </summary> /// <param name="id">欲删除的评论的ID</param> /// <returns>是否成功</returns> bool Remove(int id); /// <summary> /// 取得指定留言的全部评论 /// </summary> /// <param name="messageId">指定留言的ID</param> /// <returns>评论实体类集合</returns> IList<CommentInfo> GetByMessage(int messageId); } }

由业务逻辑确定数据访问操作 IAdminBLL 需要的数据访问操作:插入管理员,删除管理员,更新管理员信息,按 ID 取得管理员信息,按登录名与密码取得管理员,取得全部管理员 IMessageBLL 需要的数据访问操作:插入留言,删除留言,更新留言信息,按 ID 取得 留言信息,按分页取得留言 ICommentBLL 需要的数据访问操作:插入评论,删除评论,按留言取得全部评论 另外,添加管理员时需要验证是否存在同名管理员,所以需要添加一个“按登录名取得 管理员”。 对以上操作进行整理,的如下接口操作: IAdminDAL:Insert,Delete,Update,GetByID,GetByNameAndPassword,GetAll IMessageDAL:Insert,Delete,Update,GetByID,GetByPage ICommentDAL:Insert,Delete,GetByMessage 这三个接口文件放在 IDAL 工程下,具体代码如下:

IAdminDAL.cs: using using using using

System; System.Collections.Generic; System.Text; NGuestBook.Entity;

namespace NGuestBook.IDAL { /**/ /// <summary> /// 数据访问层接口-管理员 /// </summary> public interface IAdminDAL { /**/

15


基于.NET 平台的分层架构实战

果果红工作室

/// <summary> /// 插入管理员 /// </summary> /// <param name="admin">管理员实体类</param> /// <returns>是否成功</returns> bool Insert(AdminInfo admin); /**/ /// <summary> /// 删除管理员 /// </summary> /// <param name="id">欲删除的管理员的ID</param> /// <returns>是否成功</returns> bool Delete(int id); /**/ /// <summary> /// 更新管理员信息 /// </summary> /// <param name="admin">管理员实体类</param> /// <returns>是否成功</returns> bool Update(AdminInfo admin); /**/ /// <summary> /// 按ID取得管理员信息 /// </summary> /// <param name="id">管理员ID</param> /// <returns>管理员实体类</returns> AdminInfo GetByID(int id); /**/ /// <summary> /// 按管理员名取得管理员信息 /// </summary> /// <param name="name">管理员名</param> /// <returns>管理员实体类</returns> AdminInfo GetByName(string name); /**/ /// <summary> /// 按用户名及密码取得管理员信息 /// </summary> /// <param name="name">用户名</param> /// <param name="password">密码</param> /// <returns>管理员实体类,不存在时返回null</returns> AdminInfo GetByNameAndPassword(string name, string password); /**/ /// <summary> /// 取得全部管理员信息 /// </summary> /// <returns>管理员实体类集合</returns>

16


基于.NET 平台的分层架构实战

果果红工作室

IList<AdminInfo> GetAll(); } }

IMessageDAL.cs: using using using using

System; System.Collections.Generic; System.Text; NGuestBook.Entity;

namespace NGuestBook.IDAL { /**/ /// <summary> /// 数据访问层接口-留言 /// </summary> public interface IMessageDAL { /**/ /// <summary> /// 插入留言 /// </summary> /// <param name="message">留言实体类</param> /// <returns>是否成功</returns> bool Insert(MessageInfo message); /**/ /// <summary> /// 删除留言 /// </summary> /// <param name="id">欲删除的留言的ID</param> /// <returns>是否成功</returns> bool Delete(int id); /**/ /// <summary> /// 更新留言信息 /// </summary> /// <param name="message">留言实体类</param> /// <returns>是否成功</returns> bool Update(MessageInfo message); /**/ /// <summary> /// 按ID取得留言信息 /// </summary> /// <param name="id">留言ID</param> /// <returns>留言实体类</returns> MessageInfo GetByID(int id); /**/ /// <summary>

17


基于.NET 平台的分层架构实战

果果红工作室

/// 按分页取得留言信息 /// </summary> /// <param name="pageSize">每页显示几条留言</param> /// <param name="pageNumber">当前页码</param> /// <returns>留言实体类集合</returns> IList<MessageInfo> GetByPage(int pageSize, int pageNumber); } }

ICommentDAL.cs: using System; using System.Collections.Generic; using System.Text; using NGuestBook.Entity; namespace NGuestBook.IDAL { /**/ /// <summary> /// 数据访问层接口-评论 /// </summary> public interface ICommentDAL { /**/ /// <summary> /// 插入评论 /// </summary> /// <param name="comment">评论实体类</param> /// <returns>是否成功</returns> bool Insert(CommentInfo comment); /**/ /// <summary> /// 删除评论 /// </summary> /// <param name="id">欲删除的评论的ID</param> /// <returns>是否成功</returns> bool Delete(int id); /**/ /// <summary> /// 取得指定留言的全部评论 /// </summary> /// <param name="messageId">指定留言的ID</param> /// <returns>评论实体类集合</returns>

18


基于.NET 平台的分层架构实战

果果红工作室

IList<CommentInfo> GetByMessage(int messageId); } }

六、 依赖注入机制及 IoC 的设计与实现 我们设计的分层架构,层与层之间应该是松散耦合的。因为是单向单一调用,所以,这 里的“松散耦合”实际是指上层类不能具体依赖于下层类,而应该依赖于下层提供的一个接 口。这样,上层类不能直接实例化下层中的类,而只持有接口,至于接口所指变量最终究竟 是哪一个类,则由依赖注入机制决定。 之所以这样做,是为了实现层与层之间的“可替换”式设计,例如,现在需要换一种方 式实现数据访问层,只要这个实现遵循了前面定义的数据访问层接口,业务逻辑层和表示层 不需要做任何改动,只需要改一下配置文件系统即可正常运行。另外,基于这种结构的系统, 还可以实现并行开发。即不同开发人员可以专注于自己的层次,只有接口被定义好了,开发 出来的东西就可以无缝连接。 在 J2EE 平台上,主要使用 Spring 框架实现依赖注入。这里,我们将自己做一个依赖 注入容器。 依赖注入的理论基础是 Abstract Factory 设计模式,这里结合具体实例简单介绍一下。

上图以数据访问层为例,展示了 Abstract Factory 模式的应用。如图,现假设有针对 Access 和 SQLServer 两种数据库的数据访问层,它们都实现了数据访问层接口。每个数据 访问层有自己的工厂,所有工厂都实现自 IDALFactory 接口。而客户类(这里就是业务逻

19


基于.NET 平台的分层架构实战

果果红工作室

辑层类)仅与工厂接口、数据访问层接口耦合,而与具体类无关,这样,只要通过配置文件 确定实例化哪个工厂,就可以得到不同的数据访问层。 然而,这种设计虽然可行,但是代码比较冗余,因为这样需要为数据访问层的每一个实 现编写一个工厂,业务逻辑层也一样。在以前,我们毫无办法,但是,.NET 平台引入的反 射机制,给我们提供了一种解决方案。使用反射,每个层只需要一个工厂,然后通过从配置 文件中读出程序集的名称,动态加载相应类。另外,为了提高依赖注入机制的效率,这里引 入缓存机制。下面来看具体实现。

配置 首先,需要在 Web 工程的 Web.config 文件的<appSettings>节点下添加如下两个项: <add key="DAL" value=""/> <add key="BLL" value=""/> 这两个配置选项分别存储要应用的数据访问和也业务逻辑层的程序集名称。value 目前 是空,是因为目前还没有各个层次的具体实现。

实现缓存操作辅助类 为实现缓存操作,我们将缓存操作封装成一个辅助类,放在 Utility 工程下,具体代码 如下:

CacheAccess.cs: using System; using System.Web; using System.Web.Caching; namespace NGuestBook.Utility { /**/ /// <summary> /// 辅助类,用于缓存操作 /// </summary> public sealed class CacheAccess { /**/ /// <summary> /// 将对象加入到缓存中 /// </summary> /// <param name="cacheKey">缓存键</param> /// <param name="cacheObject">缓存对象</param> /// <param name="dependency">缓存依赖项</param> public static void SaveToCache(string cacheKey, object cacheObject, CacheDependency dependency) {

20


基于.NET 平台的分层架构实战

果果红工作室

Cache cache = HttpRuntime.Cache; cache.Insert(cacheKey, cacheObject, dependency); } /**/ /// <summary> /// 从缓存中取得对象,不存在则返回null /// </summary> /// <param name="cacheKey">缓存键</param> /// <returns>获取的缓存对象</returns> public static object GetFromCache(string cacheKey) { Cache cache = HttpRuntime.Cache; return cache[cacheKey]; } } }

封装依赖注入代码 因为很多依赖注入代码非常相似,为了减少重复性代码,我们将可复用的代码先封装在 一个类中。具体代码如下(这个类放在 Factory 工程下):

DependencyInjector.cs: using using using using using using

System; System.Configuration; System.Reflection; System.Web; System.Web.Caching; NGuestBook.Utility;

namespace NGuestBook.Factory { /**/ /// <summary> /// 依赖注入提供者 /// 使用反射机制实现 /// </summary> public sealed class DependencyInjector { /**/ /// <summary> /// 取得数据访问层对象 /// 首先检查缓存中是否存在,如果不存在,则利用反射机制返回对象 /// </summary> /// <param name="className">数据访问类名称</param> /// <returns>数据访问层对象</returns>

21


基于.NET 平台的分层架构实战

果果红工作室

public static object GetDALObject(string className) { /**/ /// <summary> /// 取得数据访问层名称,首先检查缓存,不存在则到配置文件中读取 /// 缓存依赖项为Web.Config文件 /// </summary> 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); } /**/ /// <summary> /// 取得数据访问层对象 /// </summary> string dalName = (string)dal; 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); } return dalObject; } /**/ /// <summary> /// 取得业务逻辑层对象 /// 首先检查缓存中是否存在,如果不存在,则利用反射机制返回对象 /// </summary> /// <param name="className">业务逻辑类名称</param> /// <returns>业务逻辑层对象</returns> public static object GetBLLObject(string className) { /**/ /// <summary> /// 取得业务逻辑层名称,首先检查缓存,不存在则到配置文件中读取 /// 缓存依赖项为Web.Config文件 /// </summary> object bll = CacheAccess.GetFromCache("BLL"); if (bll == null) { CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config"));

22


基于.NET 平台的分层架构实战

果果红工作室

bll = ConfigurationManager.AppSettings["BLL"]; CacheAccess.SaveToCache("BLL", bll, fileDependency); } /**/ /// <summary> /// 取得业务逻辑层对象 /// </summary> string bllName = (string)bll; 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); } return bllObject; } } }

实现工厂 下面使用两个辅助类,实现数据访问层工厂和业务逻辑层工厂。

DALFactory.cs using System; using NGuestBook.IDAL; namespace NGuestBook.Factory { /**/ /// <summary> /// 数据访问层工厂,用于获取相应的数据访问层对象 /// 使用Abstract Factory设计模式+Facace设计模式+反射机制+缓存机制设计 /// </summary> public sealed class DALFactory { /**/ /// <summary> /// 获取管理员数据访问层对象 /// </summary> /// <returns>管理员数据访问层对象</returns> public static IAdminDAL CreateAdminDAL() { return (IAdminDAL)DependencyInjector.GetDALObject("AdminDAL");

23


基于.NET 平台的分层架构实战

果果红工作室

} /**/ /// <summary> /// 获取留言数据访问层对象 /// </summary> /// <returns>留言数据访问层对象</returns> public static IMessageDAL CreateMessageDAL() { return (IMessageDAL)DependencyInjector.GetDALObject("MessageDAL"); } /**/ /// <summary> /// 获取评论数据访问层对象 /// </summary> /// <returns>评论数据访问层对象</returns> public static ICommentDAL CreateCommentDAL() { return (ICommentDAL)DependencyInjector.GetDALObject("CommentDAL"); } } }

BLLFactory.cs using System; using NGuestBook.IBLL; namespace NGuestBook.Factory { /**/ /// <summary> /// 业务逻辑层工厂,用于获取相应的业务逻辑层对象 /// 使用Abstract Factory设计模式+Facace设计模式+反射机制+缓存机制设计 /// </summary> public sealed class BLLFactory { /**/ /// <summary> /// 获取管理员业务逻辑层对象 /// </summary> /// <returns>管理员业务逻辑层对象</returns> public static IAdminBLL CreateAdminBLL() {

24


基于.NET 平台的分层架构实战

果果红工作室

return (IAdminBLL)DependencyInjector.GetBLLObject("AdminBLL"); } /**/ /// <summary> /// 获取留言业务逻辑层对象 /// </summary> /// <returns>留言业务逻辑层对象</returns> public static IMessageBLL CreateMessageBLL() { return (IMessageBLL)DependencyInjector.GetBLLObject("MessageBLL"); } /**/ /// <summary> /// 获取评论业务逻辑层对象 /// </summary> /// <returns>评论业务逻辑层对象</returns> public static ICommentBLL CreateCommentBLL() { return (ICommentBLL)DependencyInjector.GetBLLObject("CommentBLL"); } } }

七、 数据访问层的第一种实现:Access+动态生成 SQL 语 言 经过上面篇文章的介绍,整个系统的框架算是基本搭建完了,下面,我们要具体实现各 个层次。关于数据访问层的实现,我准备讨论三种实现方式,这一篇文章讨论第一种: Access+动态生成 SQL。 顾名思义,这种实现将使用 Access 作为后台数据库,而操作方式也是最基本的使用 SQL 命令。 在具体编写实现代码之前,我们需要做一些准备工作:

第一步,我们要将 Access 数据库搭建完成,具体做法如下。 在 Web 工程下新建一个文件夹,命名为 AccessData,并在其中新建一个 mdb 文件 (即 Access 数据库文件) ,按照前面介绍过的数据库设计构架,将数据表及表间关系建好, 这里不再赘述。

第二步,我们要进行一些配置。 打开 Web 工程下的 Web.config 文件, 在其中的 appSettings 节点下,添加如下键值:

25


基于.NET 平台的分层架构实战 <add

果果红工作室

key="AccessConnectionString"

value="Provider=Microsoft.Jet.OLEDB.4.0;Data

Source={DBPath}"/> <add key="AccessPath" value="~/AccessData/AccessDatabase.mdb"/>

第一条为 Access 的连接字符串,第二条为 Access 数据库文件的路径,其中“~”表 示网站根目录。

第三步,新建一个工程。 我们要新建一个工程 AccessDAL,用来存放 Access 数据访问层的代码。 准备工作做完了,现在来实现具体的代码。

1.编写数据访问助手类 因为很多数据访问操作流程很相似,所以,这里将一些可复用的代码抽取出来,编写成 助手类,以此减少代码量,提高代码复用性。 这个助手类放在 AccessDAL 下,叫 AccessDALHelper,主要负责 Access 数据库的访 问。它包括三个方法: GetConnectionString:从配置文件中读取配置项,组合成连接字符串。 ExecuteSQLNonQuery: 执行指定 SQL 语句,不返回任何值,一般用于 Insert,Delete, Update 命令。 ExecuteSQLDataReader:执行 SQL 语句返回查询结果,一般用于 Select 命令。 具体代码如下:

AccessDALHelper.cs: using using using using using using using

System; System.Web; System.Web.Caching; System.Configuration; System.Data; System.Data.OleDb; NGuestBook.Utility;

namespace NGuestBook.AccessDAL { /**/ /// <summary> /// Access数据库操作助手 /// </summary> public sealed class AccessDALHelper { /**/ /// <summary> /// 读取Access数据库的连接字符串 /// 首先从缓存里读取,如果不存在则到配置文件中读取,并放入缓存 /// </summary> /// <returns>Access数据库的连接字符串</returns> private static string GetConnectionString()

26


基于.NET 平台的分层架构实战

果果红工作室

{ if (CacheAccess.GetFromCache("AccessConnectionString") != null) { return CacheAccess.GetFromCache("AccessConnectionString").ToString(); } else { string dbPath = ConfigurationManager.AppSettings["AccessPath"]; string dbAbsolutePath = HttpContext.Current.Server.MapPath(dbPath); string connectionString = ConfigurationManager.AppSettings["AccessConnectionString"]; CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config")); CacheAccess.SaveToCache("AccessConnectionString", connectionString.Replace("{DBPath}", dbAbsolutePath), fileDependency); return connectionString.Replace("{DBPath}", dbAbsolutePath); } } /**/ /// <summary> /// 执行SQL语句并且不返回任何值 /// </summary> /// <param name="SQLCommand">所执行的SQL命令</param> /// <param name="parameters">参数集合</param> public static void ExecuteSQLNonQuery(string SQLCommand, OleDbParameter[] parameters) { OleDbConnection connection = new OleDbConnection(GetConnectionString()); OleDbCommand command = new OleDbCommand(SQLCommand, connection); for (int i = 0; i < parameters.Length; i++) { command.Parameters.Add(parameters[i]); } connection.Open(); command.ExecuteNonQuery(); connection.Close(); } /**/ /// <summary> /// 执行SQL语句并返回包含查询结果的DataReader /// </summary> /// <param name="SQLCommand">所执行的SQL命令</param> /// <param name="parameters">参数集合</param> /// <returns></returns> public static OleDbDataReader ExecuteSQLDataReader(string SQLCommand, OleDbParameter[] parameters)

27


基于.NET 平台的分层架构实战

果果红工作室

{ OleDbConnection connection = new OleDbConnection(GetConnectionString()); OleDbCommand command = new OleDbCommand(SQLCommand, connection); for (int i = 0; i < parameters.Length; i++) { command.Parameters.Add(parameters[i]); } connection.Open(); OleDbDataReader dataReader = command.ExecuteReader(); //connection.Close(); return dataReader; } } }

2.实现具体的数据访问操作类 因为前面已经定义了数据访问层接口,所以实现数据访问操作类就是很机械的工作了。 下面仅以 Admin 的数据访问操作类为例:

AdminDAL: using using using using using using using

System; System.Collections.Generic; System.Text; System.Data; System.Data.OleDb; NGuestBook.IDAL; NGuestBook.Entity;

namespace NGuestBook.AccessDAL { public class AdminDAL : IAdminDAL { /**/ /// <summary> /// 插入管理员 /// </summary> /// <param name="admin">管理员实体类</param> /// <returns>是否成功</returns> public bool Insert(AdminInfo admin) { string SQLCommand = "insert into [TAdmin]([Name],[Password]) values(@name,@password)"; OleDbParameter[] parameters ={ new OleDbParameter("name",admin.Name), new OleDbParameter("password",admin.Password) }; try {

28


基于.NET 平台的分层架构实战

果果红工作室

AccessDALHelper.ExecuteSQLNonQuery(SQLCommand, parameters); return true; } catch { return false; } } /**/ /// <summary> /// 删除管理员 /// </summary> /// <param name="id">欲删除的管理员的ID</param> /// <returns>是否成功</returns> public bool Delete(int id) { string SQLCommand = "delete from [TAdmin] where [ID]=@id"; OleDbParameter[] parameters ={ new OleDbParameter("id",id) }; try { AccessDALHelper.ExecuteSQLNonQuery(SQLCommand, parameters); return true; } catch { return false; } } /**/ /// <summary> /// 更新管理员信息 /// </summary> /// <param name="admin">管理员实体类</param> /// <returns>是否成功</returns> public bool Update(AdminInfo admin) { string SQLCommand = "update [TAdmin] set [Name]=@name,[Password]=@password where [ID]=@id"; OleDbParameter[] parameters ={ new OleDbParameter("id",admin.ID), new OleDbParameter("name",admin.Name), new OleDbParameter("password",admin.Password) }; try { AccessDALHelper.ExecuteSQLNonQuery(SQLCommand, parameters); return true; }

29


基于.NET 平台的分层架构实战

果果红工作室

catch { return false; } } /**/ /// <summary> /// 按ID取得管理员信息 /// </summary> /// <param name="id">管理员ID</param> /// <returns>管理员实体类</returns> public AdminInfo GetByID(int id) { string SQLCommand = "select * from [TAdmin] where [ID]=@id"; OleDbParameter[] parameters ={ new OleDbParameter("id",id) }; try { OleDbDataReader dataReader = AccessDALHelper.ExecuteSQLDataReader(SQLCommand, parameters); if (!dataReader.HasRows) { throw new Exception(); } AdminInfo admin = new AdminInfo(); dataReader.Read(); admin.ID = (int)dataReader["ID"]; admin.Name = (string)dataReader["Name"]; admin.Password = (string)dataReader["Password"]; return admin; } catch { return null; } } /**/ /// <summary> /// 按用户名及密码取得管理员信息 /// </summary> /// <param name="name">用户名</param> /// <param name="password">密码</param> /// <returns>管理员实体类,不存在时返回null</returns> public AdminInfo GetByNameAndPassword(string name, string password) { string SQLCommand = "select * from [TAdmin] where [Name]=@name and [Password]=@password"; OleDbParameter[] parameters ={ new OleDbParameter("name",name),

30


基于.NET 平台的分层架构实战

果果红工作室

new OleDbParameter("password",password), }; try { OleDbDataReader dataReader = AccessDALHelper.ExecuteSQLDataReader(SQLCommand, parameters); if (!dataReader.HasRows) { throw new Exception(); } AdminInfo admin = new AdminInfo(); dataReader.Read(); admin.ID = (int)dataReader["ID"]; admin.Name = (string)dataReader["Name"]; admin.Password = (string)dataReader["Password"]; return admin; } catch { return null; } } /**/ /// <summary> /// 按管理员名取得管理员信息 /// </summary> /// <param name="name">管理员名</param> /// <returns>管理员实体类</returns> public AdminInfo GetByName(string name) { string SQLCommand = "select * from [TAdmin] where [Name]=@name"; OleDbParameter[] parameters ={ new OleDbParameter("name",name), }; try { OleDbDataReader dataReader = AccessDALHelper.ExecuteSQLDataReader(SQLCommand, parameters); if (!dataReader.HasRows) { throw new Exception(); } AdminInfo admin = new AdminInfo(); dataReader.Read(); admin.ID = (int)dataReader["ID"]; admin.Name = (string)dataReader["Name"]; admin.Password = (string)dataReader["Password"]; return admin; } catch { return null;

31


基于.NET 平台的分层架构实战

果果红工作室

} } /**/ /// <summary> /// 取得全部管理员信息 /// </summary> /// <returns>管理员实体类集合</returns> public IList<AdminInfo> GetAll() { string SQLCommand = "select * from [TAdmin]"; try { OleDbDataReader dataReader = AccessDALHelper.ExecuteSQLDataReader(SQLCommand, null); if (!dataReader.HasRows) { throw new Exception(); } IList<AdminInfo> adminCollection = new List<AdminInfo>(); int i = 0; while (dataReader.Read()) { AdminInfo admin = new AdminInfo(); admin.ID = (int)dataReader["ID"]; admin.Name = (string)dataReader["Name"]; admin.Password = (string)dataReader["Password"]; adminCollection.Add(admin); i++; } return adminCollection; } catch { return null; } } } }

可以看到,这里主要包括三种类型的操作,一种是修改型,如 Insert;一种是返回单个 实体类型,如 GetByID;还有一种是返回实体类集合型,如 GetAll。 MessageDAL 和 CommentDAL 的实现非常相似,在这里不再赘述。

八、 对数据访问层第一种实现 昨天的文章基于.NET 平台的分层架构实战(七)——数据访问层的第一种实现: Access+SQL 发布后,很多朋友对我的程序提出了意见和建议,在这里先谢谢你们!对我 的程序提出了很多建设性的意见。

32


基于.NET 平台的分层架构实战

果果红工作室

我大体总结了一下,昨天程序的主要缺点有: 1.Connection 对象没有关闭 2.DataReader 对象没有关闭 3.相似代码太多,造成代码冗余。 其中第一点问题,目前还没有太好的解决方案,主要是因为 Connection 一旦关闭, DataReader 就无法读取了。而且,Connection 对象应该会自动在适当的时候关闭(通过 观察临时文件得出),并且在实际运行时并无影响(在功能上),所以这里没有专门解决。而 针对后面两个问题,我使用了如下解决方案。 对于关闭 DataReader 的方法,实现起来很简单,在 finally 里将他关闭就行了。关键 是如何去处冗余代码。 经过我的分析,数据访问层的操作可以分为三类:不返回数据,返回单个实体类,返回 实体类集合。我将这三种操作的公共部分抽出,写成三个方法放在 AccessDALHelper 里, 类型不同的问题使用泛型解决。 这样做有一个难题,就是不同实体在由 DataReader 转化为实体类时的代码很不一样, 无法抽出。这里,我使用了 Strategy 模式解决。具体做法是:首先定义一个由 DataReader 转换为实体类的策略接口,然后为不同的实体编写不同的转换策略,示意图如下:

可以看出,所有转换策略都要实现 IDataReaderToEntityStrategy 接口,并且每个策 略都有一个自组合,这是以为他们都要实现 Singleton 模式。而 AccessDALHelper 与具体 策略无关,仅与接口耦合。 下面来看一下具体代码:

首先是 IDataReaderToEntityStrategy 接口 IDataReaderToEntityStrategy.cs: using System; using System.Collections.Generic;

33


基于.NET 平台的分层架构实战

果果红工作室

using System.Text; using System.Data; using System.Data.OleDb; namespace NGuestBook.AccessDAL { /**/ /// <summary> /// 由DataReader转换成实体类的策略接口 /// </summary> public interface IDataReaderToEntityStrategy<T> { /**/ /// <summary> /// 将DataReader转换为实体类,采用泛型 /// </summary> /// <param name="dataReader">包含数据的DataReader对象</param> /// <returns>实体类</returns> T DataReaderToEntity(OleDbDataReader dataReader); } }

然后以 Admin 为例,看一下策略的具体实现: AdminDataReaderToEntityStrategy.cs: using using using using using using

System; System.Collections.Generic; System.Text; System.Data; System.Data.OleDb; NGuestBook.Entity;

namespace NGuestBook.AccessDAL { /**/ /// <summary> /// DataReader到实体类的转换策略-管理员 /// 实现上使用Singleton模式,保证全局唯一实例 /// </summary> public class AdminDataReaderToEntityStrategy : IDataReaderToEntityStrategy<AdminInfo> { private static AdminDataReaderToEntityStrategy singleInstance = null; /**/ /// <summary> /// 私有化构造函数,保证无法外部实例化 /// </summary> private AdminDataReaderToEntityStrategy() { } /**/ /// <summary>

34


基于.NET 平台的分层架构实战

果果红工作室

/// 静态方法,用于取得全局唯一实例 /// </summary> /// <returns>全局唯一实例</returns> public static AdminDataReaderToEntityStrategy GetInstance() { if (singleInstance == null) { singleInstance = new AdminDataReaderToEntityStrategy(); } return singleInstance; } /**/ /// <summary> /// 由DataReader转换到管理员实体类 /// </summary> /// <param name="dataReader">包含数据的DataReader对象</param> /// <returns>管理员实体类</returns> public AdminInfo DataReaderToEntity(OleDbDataReader dataReader) { AdminInfo admin = new AdminInfo(); admin.ID = (int)dataReader["ID"]; admin.Name = (string)dataReader["Name"]; admin.Password = (string)dataReader["Password"]; return admin; } } }

可以看到,这里实现了一个单件模式。

下一个,是重构后的 AccessDALHelper,增加了三个方法。 AccessDALHelper.cs: using using using using using using using using

System; System.Collections.Generic; System.Web; System.Web.Caching; System.Configuration; System.Data; System.Data.OleDb; NGuestBook.Utility;

namespace NGuestBook.AccessDAL { /**/ /// <summary> /// Access数据库操作助手 /// </summary> public sealed class AccessDALHelper

35


基于.NET 平台的分层架构实战

果果红工作室

{ /**/ /// <summary> /// 读取Access数据库的连接字符串 /// 首先从缓存里读取,如果不存在则到配置文件中读取,并放入缓存 /// </summary> /// <returns>Access数据库的连接字符串</returns> private static string GetConnectionString() { if (CacheAccess.GetFromCache("AccessConnectionString") != null) { return CacheAccess.GetFromCache("AccessConnectionString").ToString(); } else { string dbPath = ConfigurationManager.AppSettings["AccessPath"]; string dbAbsolutePath = HttpContext.Current.Server.MapPath(dbPath); string connectionString = ConfigurationManager.AppSettings["AccessConnectionString"]; CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config")); CacheAccess.SaveToCache("AccessConnectionString", connectionString.Replace("{DBPath}", dbAbsolutePath), fileDependency); return connectionString.Replace("{DBPath}", dbAbsolutePath); } } /**/ /// <summary> /// 执行SQL语句并且不返回任何值 /// </summary> /// <param name="SQLCommand">所执行的SQL命令</param> /// <param name="parameters">参数集合</param> public static void ExecuteSQLNonQuery(string SQLCommand, OleDbParameter[] parameters) { OleDbConnection connection = new OleDbConnection(GetConnectionString()); OleDbCommand command = new OleDbCommand(SQLCommand, connection); for (int i = 0; i < parameters.Length; i++) { command.Parameters.Add(parameters[i]); } connection.Open(); command.ExecuteNonQuery(); connection.Close(); } /**/

36


基于.NET 平台的分层架构实战

果果红工作室

/// <summary> /// 执行SQL语句并返回包含查询结果的DataReader /// </summary> /// <param name="SQLCommand">所执行的SQL命令</param> /// <param name="parameters">参数集合</param> /// <returns></returns> public static OleDbDataReader ExecuteSQLDataReader(string SQLCommand, OleDbParameter[] parameters) { OleDbConnection connection = new OleDbConnection(GetConnectionString()); OleDbCommand command = new OleDbCommand(SQLCommand, connection); for (int i = 0; i < parameters.Length; i++) { command.Parameters.Add(parameters[i]); } connection.Open(); OleDbDataReader dataReader = command.ExecuteReader(); //connection.Close(); return dataReader; } /**/ /// <summary> /// 执行不需要返回数据的操作 /// </summary> /// <param name="SQLCommand">SQL命令</param> /// <param name="parameters">参数</param> /// <returns>是否成功</returns> public static bool OperateNonData(string SQLCommand, OleDbParameter[] parameters) { try { ExecuteSQLNonQuery(SQLCommand, parameters); return true; } catch { return false; } } /**/ /// <summary> /// 执行返回单个实体类的操作 /// </summary> /// <typeparam name="T">实体类类型</typeparam> /// <param name="SQLCommand">SQL命令</param> /// <param name="parameters">参数</param> /// <param name="strategy">DataReader到实体类的转换策略</param>

37


基于.NET 平台的分层架构实战

果果红工作室

/// <returns>实体类</returns> public static T OperateEntity<T>(string SQLCommand, OleDbParameter[] parameters, IDataReaderToEntityStrategy<T> strategy) { OleDbDataReader dataReader = ExecuteSQLDataReader(SQLCommand, parameters); try { if (!dataReader.HasRows) { throw new Exception(); } dataReader.Read(); return strategy.DataReaderToEntity(dataReader); } catch { return default(T); } finally { dataReader.Close(); } } /**/ /// <summary> /// 执行返回实体类集合的操作 /// </summary> /// <typeparam name="T">实体类类型</typeparam> /// <param name="SQLCommand">SQL命令</param> /// <param name="parameters">参数</param> /// <param name="strategy">DataReader到实体类的转换策略</param> /// <returns>实体类</returns> public static IList<T> OperateEntityCollection<T>(string SQLCommand, OleDbParameter[] parameters, IDataReaderToEntityStrategy<T> strategy) { OleDbDataReader dataReader = AccessDALHelper.ExecuteSQLDataReader(SQLCommand, null); try { if (!dataReader.HasRows) { throw new Exception(); } IList<T> entityCollection = new List<T>(); int i = 0; while (dataReader.Read()) { entityCollection.Add(strategy.DataReaderToEntity(dataReader)); i++;

38


基于.NET 平台的分层架构实战

果果红工作室

} return entityCollection; } catch { return default(IList<T>); } finally { dataReader.Close(); } } } }

最后以 Admin 为例,看一下简化后的数据访问层实现: AdminDAL.cs: using using using using using using using

System; System.Collections.Generic; System.Text; System.Data; System.Data.OleDb; NGuestBook.IDAL; NGuestBook.Entity;

namespace NGuestBook.AccessDAL { public class AdminDAL : IAdminDAL { /// /// 插入管理员 /// /// 管理员实体类 /// 是否成功 public bool Insert(AdminInfo admin) { string SQLCommand = "insert into [TAdmin]([Name],[Password]) values(@name,@password)"; OleDbParameter[] parameters ={ new OleDbParameter("name",OleDbType.VarChar,20), new OleDbParameter("password",OleDbType.VarChar,50) }; parameters[0].Value = admin.Name; parameters[1].Value = admin.Password; return AccessDALHelper.OperateNonData(SQLCommand, parameters); } /// /// 删除管理员

39


基于.NET 平台的分层架构实战

果果红工作室

/// /// 欲删除的管理员的ID /// 是否成功 public bool Delete(int id) { string SQLCommand = "delete from [TAdmin] where [ID]=@id"; OleDbParameter[] parameters ={ new OleDbParameter("id",OleDbType.Integer) }; parameters[0].Value = id; return AccessDALHelper.OperateNonData(SQLCommand, parameters); } /// /// 更新管理员信息 /// /// 管理员实体类 /// 是否成功 public bool Update(AdminInfo admin) { string SQLCommand = "update [TAdmin] set [Name]=@name,[Password]=@password where [ID]=@id"; OleDbParameter[] parameters ={ new OleDbParameter("id",OleDbType.Integer), new OleDbParameter("name",OleDbType.VarChar,20), new OleDbParameter("password",OleDbType.VarChar,50) }; parameters[0].Value = admin.ID; parameters[1].Value = admin.Name; parameters[2].Value = admin.Password; return AccessDALHelper.OperateNonData(SQLCommand, parameters); } /// /// 按ID取得管理员信息 /// /// 管理员ID /// 管理员实体类 public AdminInfo GetByID(int id) { string SQLCommand = "select * from [TAdmin] where [ID]=@id"; OleDbParameter[] parameters ={ new OleDbParameter("id",OleDbType.Integer) }; parameters[0].Value = id; return AccessDALHelper.OperateEntity<AdminInfo>(SQLCommand, parameters, AdminDataReaderToEntityStrategy.GetInstance()); } /// /// 按用户名及密码取得管理员信息 /// /// 用户名

40


基于.NET 平台的分层架构实战

果果红工作室

/// 密码 /// 管理员实体类,不存在时返回null public AdminInfo GetByNameAndPassword(string name, string password) { string SQLCommand = "select * from [TAdmin] where [Name]=@name and [Password]=@password"; OleDbParameter[] parameters ={ new OleDbParameter("name",OleDbType.VarChar,20), new OleDbParameter("password",OleDbType.VarChar,50) }; parameters[0].Value = name; parameters[1].Value = password; return AccessDALHelper.OperateEntity<AdminInfo>(SQLCommand, parameters, AdminDataReaderToEntityStrategy.GetInstance()); } /// /// 按管理员名取得管理员信息 /// /// 管理员名 /// 管理员实体类 public AdminInfo GetByName(string name) { string SQLCommand = "select * from [TAdmin] where [Name]=@name"; OleDbParameter[] parameters ={ new OleDbParameter("name",OleDbType.VarChar,20) }; parameters[0].Value = name; return AccessDALHelper.OperateEntity<AdminInfo>(SQLCommand, parameters, AdminDataReaderToEntityStrategy.GetInstance()); } /// /// 取得全部管理员信息 /// /// 管理员实体类集合 public IList<AdminInfo> GetAll() { string SQLCommand = "select * from [TAdmin]"; return AccessDALHelper.OperateEntityCollection<AdminInfo>(SQLCommand, null, AdminDataReaderToEntityStrategy.GetInstance()); } } }

九、 数据访问层的第二种实现:SQLServer+存储过程 在上一篇中,讨论了使用 SQL 构建数据访问层的方法,并且针对的是 Access 数据库。 而这一篇中,将要创建一个针对 SQLServer 数据库的数据访问层,并且配合存储过程实现。 曾经有朋友问我使用 SQL 和存储过程在效率上的差别,惭愧的是我对这方面没有研究, 也没有实际做过测试。通过查阅资料,发现在一般情况下,存储过程的效率由于使用 SQL,

41


基于.NET 平台的分层架构实战

果果红工作室

但是也不绝对,也发现有的朋友测试时发现在特定情况下 SQL 的效率优于存储过程,所以 这个问题不能一概而论。 好,废话不多说,这里先列出使用存储过程构建数据访问层的一般步骤: 1.创建新工程 2.创建数据库 3.编写相应存储过程 4.编写数据库辅助类 5.实现数据访问层

创建新工程 在开始所有开发工作前,我们需要在解决方案下新建一个工程,叫 SQLServerDAL, 用于存放所有 SQLServer 数据访问层的代码。

创建数据库 首先,我们要根据前文设计的数据库,在 SQLServer 中创建相应的数据库及数据表。 我使用的是 SQLServer2005,使用企业管理器创建,创建方法不再赘述。

编写存储过程 数据库创建完成后,我们就要编写存储过程了。由于数据访问层接口已经确定,所以需 要哪些存储过程也很好确定。例如数据访问层接口中有一个添加管理员方法,那么就一定有 一个存储过程实现这个功能。 还是以管理员模块为例,经过简单分析,需要一下存储过程: 插入管理员记录 删除管理员记录 更新管理员信息 按 ID 取得管理员记录 按用户名及密码取得管理员记录 按用户名取得管理员记录 取得全部管理员记录 创建这些存储过程的 SQL 代码如下:

PROCEDURE [dbo].[Pr_InsertAdmin] set ANSI_NULLS ON set QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <T2噬菌体> -- Create date: <2008-07-04> -- Description: <插入管理员记录> -- ============================================= CREATE PROCEDURE [dbo].[Pr_InsertAdmin]

42


基于.NET 平台的分层架构实战

果果红工作室

( @Name Nvarchar(20), @Password Nvarchar(50) ) AS INSERT INTO TAdmin ( [Name], [Password] ) VALUES ( @Name, @Password )

PROCEDURE [dbo].[Pr_DeleteAdmin] set ANSI_NULLS ON set QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <T2噬菌体> -- Create date: <2008-07-04> -- Description: <删除管理员记录> -- ============================================= CREATE PROCEDURE [dbo].[Pr_DeleteAdmin] ( @ID Int ) AS DELETE FROM TAdmin WHERE [ID]=@ID

PROCEDURE [dbo].[Pr_UpdateAdmin] set ANSI_NULLS ON set QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <T2噬菌体> -- Create date: <2008-07-04> -- Description: <修改管理员记录> -- ============================================= CREATE PROCEDURE [dbo].[Pr_UpdateAdmin] ( @ID Int, @Name Nvarchar(20), @Password Nvarchar(50) ) AS UPDATE TAdmin SET [Name]=@Name, [Password]=@Password WHERE [ID]=@ID

43


基于.NET 平台的分层架构实战

果果红工作室

PROCEDURE [dbo].[Pr_GetAdminByID] set ANSI_NULLS ON set QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <T2噬菌体> -- Create date: <2008-07-04> -- Description: <按ID取得管理员信息> -- ============================================= CREATE PROCEDURE [dbo].[Pr_GetAdminByID] ( @ID Int ) AS SELECT * FROM TAdmin WHERE [ID]=@ID

PROCEDURE

[dbo].[Pr_GetAdminByNameAndPassword]

set ANSI_NULLS ON set QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <T2噬菌体> -- Create date: <2008-07-04> -- Description: <按用户名及密码取得管理员信息> -- ============================================= CREATE PROCEDURE [dbo].[Pr_GetAdminByNameAndPassword] ( @Name Nvarchar(20), @Password Nvarchar(50) ) AS SELECT * FROM TAdmin WHERE [Name]=@Name AND [Password]=@Password

PROCEDURE [dbo].[Pr_GetAdminByName] set ANSI_NULLS ON set QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <T2噬菌体> -- Create date: <2008-07-04> -- Description: <按用户名取得管理员信息> -- ============================================= CREATE PROCEDURE [dbo].[Pr_GetAdminByName] ( @Name Nvarchar(20) ) AS SELECT * FROM TAdmin WHERE [Name]=@Name

44


基于.NET 平台的分层架构实战

果果红工作室

PROCEDURE [dbo].[Pr_GetAllAdmin] set ANSI_NULLS ON set QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <T2噬菌体> -- Create date: <2008-07-04> -- Description: <取得全部管理员信息> -- ============================================= CREATE PROCEDURE [dbo].[Pr_GetAllAdmin] AS SELECT * FROM TAdmin

编写数据库辅助类 由于访问数据库的代码很相似,这里我们仍需要编写一个数据库辅助类,来将常用代码 封装起来,方便复用。虽然在这里只使用到了存储过程,但是为了扩展性考虑,这个数据库 辅助类仍然包含了通过 SQL 访问数据库的方法。具体实现如下:

SQLServerDALHelper.cs: using using using using using

System; System.Collections.Generic; System.Configuration; System.Data; System.Data.SqlClient;

namespace NGuestBook.SQLServerDAL { /// /// SQLServer数据库操作助手 /// public sealed class SQLServerDALHelper { /// /// 用于连接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(); }

45


基于.NET 平台的分层架构实战

果果红工作室

/// /// 执行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) { 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) {

46


基于.NET 平台的分层架构实战

果果红工作室

foreach (SqlParameter parameter in parameters) { command.Parameters.Add(parameter); } } connection.Open(); SqlDataReader sqlReader = command.ExecuteReader(); //connection.Close(); return sqlReader; } } }

实现数据访问层 最后仍以管理员模块为例,看一下具体数据访问层的实现。

AdminDAL.cs: using using using using using using using

System; System.Collections.Generic; System.Text; System.Data; System.Data.SqlClient; NGuestBook.IDAL; NGuestBook.Entity;

namespace NGuestBook.SQLServerDAL { public class AdminDAL : IAdminDAL { /// /// 插入管理员 /// /// 管理员实体类 /// 是否成功 public bool Insert(AdminInfo admin) { SqlParameter[] parameters = { new SqlParameter("@Name",SqlDbType.NVarChar), new SqlParameter("@Password",SqlDbType.NVarChar) }; parameters[0].Value = admin.Name; parameters[1].Value = admin.Password; try { SQLServerDALHelper.ExecuteProcedureNonQurey("Pr_InsertAdmin", parameters); return true; } catch

47


基于.NET 平台的分层架构实战

果果红工作室

{ return false; } } /// /// 删除管理员 /// /// 欲删除的管理员的ID /// 是否成功 public bool Delete(int id) { SqlParameter[] parameters = { new SqlParameter("@ID",SqlDbType.Int) }; parameters[0].Value = id; try { SQLServerDALHelper.ExecuteProcedureNonQurey("Pr_DeleteAdmin", parameters); return true; } catch { return false; } } /// /// 更新管理员信息 /// /// 管理员实体类 /// 是否成功 public bool Update(AdminInfo admin) { SqlParameter[] parameters = { new SqlParameter("@ID",SqlDbType.Int), new SqlParameter("@Name",SqlDbType.NVarChar), new SqlParameter("@Password",SqlDbType.NVarChar) }; parameters[0].Value = admin.ID; parameters[1].Value = admin.Name; parameters[2].Value = admin.Password; try { SQLServerDALHelper.ExecuteProcedureNonQurey("Pr_UpdateAdmin", parameters); return true; } catch {

48


基于.NET 平台的分层架构实战

果果红工作室

return false; } } /// /// 按ID取得管理员信息 /// /// 管理员ID /// 管理员实体类 public AdminInfo GetByID(int id) { SqlParameter[] parameters = { new SqlParameter("@ID",SqlDbType.Int) }; parameters[0].Value = id; SqlDataReader dataReader = null; try { dataReader = SQLServerDALHelper.ExecuteProcedureReader("GetAdminByID", parameters); dataReader.Read(); AdminInfo admin = new AdminInfo(); admin.ID = (int)dataReader["ID"]; admin.Name = (string)dataReader["Name"]; admin.Password = (string)dataReader["Password"]; return admin; } catch { return null; } finally { dataReader.Close(); } } /// /// 按用户名及密码取得管理员信息 /// /// 用户名 /// 密码 /// 管理员实体类,不存在时返回null public AdminInfo GetByNameAndPassword(string name, string password) { SqlParameter[] parameters = { new SqlParameter("@Name",SqlDbType.NVarChar), new SqlParameter("@Password",SqlDbType.NVarChar) }; parameters[0].Value = name;

49


基于.NET 平台的分层架构实战

果果红工作室

parameters[1].Value = password; SqlDataReader dataReader = null; try { dataReader = SQLServerDALHelper.ExecuteProcedureReader("GetAdminByNameAndPassword", parameters); dataReader.Read(); AdminInfo admin = new AdminInfo(); admin.ID = (int)dataReader["ID"]; admin.Name = (string)dataReader["Name"]; admin.Password = (string)dataReader["Password"]; return admin; } catch { return null; } finally { dataReader.Close(); } } /// /// 按管理员名取得管理员信息 /// /// 管理员名 /// 管理员实体类 public AdminInfo GetByName(string name) { SqlParameter[] parameters = { new SqlParameter("@Name",SqlDbType.NVarChar) }; parameters[0].Value = name; SqlDataReader dataReader = null; try { dataReader = SQLServerDALHelper.ExecuteProcedureReader("GetAdminByName", parameters); dataReader.Read(); AdminInfo admin = new AdminInfo(); admin.ID = (int)dataReader["ID"]; admin.Name = (string)dataReader["Name"]; admin.Password = (string)dataReader["Password"]; return admin; } catch { return null; }

50


基于.NET 平台的分层架构实战

果果红工作室

finally { dataReader.Close(); } } /// /// 取得全部管理员信息 /// /// 管理员实体类集合 public IList<AdminInfo> GetAll() { SqlDataReader dataReader = null; try { dataReader = SQLServerDALHelper.ExecuteProcedureReader("GetAllAdmin", null); IList<AdminInfo> adminCollection = new List<AdminInfo>(); while (dataReader.Read()) { AdminInfo admin = new AdminInfo(); admin.ID = (int)dataReader["ID"]; admin.Name = (string)dataReader["Name"]; admin.Password = (string)dataReader["Password"]; adminCollection.Add(admin); } return adminCollection; } catch { return null; } finally { dataReader.Close(); } } } }

十、 数据访问层的第三种实现:基于 NBear 框架 前面的文章讨论了使用 SQL 语句和存储过程两种数据访问层的实现方式,这一篇里, 将讨论使用 ORM 方式实现数据访问层的方法。 对象-关系映射(Object/Relation Mapping,简称 ORM),是随着面向对象的软件开 发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法, 关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务 实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中 的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承

51


基于.NET 平台的分层架构实战

果果红工作室

关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关 系数据库数据的映射。 目前.NET 平台上有许多 ORM 框架可供选择,如 NBear、NHibernate 等等。这里我 们选择 NBear 实现 ORM。 NBear是由博客园的Teddy's Knowledge Base团队开发的一个开源框架,主要用来提 高.NET平台的开发效率,其中包含了ORM、IoC、MVP等多个组件,这里仅仅用到其中的 ORM功能。关于NBear的详细使用方法本文不再详述,请参考 NBear入门教程。NBear的 最新版本下载地址为 http://files.cnblogs.com/hjf1223/NBearV3.7.2.11_src.rar。 下面我们一步一步实现数据访问层的 ORM 实现。

1.创建实体设计工程 使用 NBear 实现 ORM 功能,首先要创建一个实体设计工程,这个工程最终不会应用 到系统中,但是必须通过它来生成 NBear 实体类以及配置文件。 首先,我们在解决方案下新建一个工程,名字为 NBearEntityDesign,并为这个工程 添加到文件 NBear.Common.Design.dll 的引用, 这个文件在 NBear 文件包的 dist 目录下。 完成后,在这个工程下新建一个 C#文件,名为 EntityDesign.cs,这个文件就是设计文 件,根据对实体和数据库的设计,编写完整代码如下:

EntityDesign.cs using using using using

System; System.Collections.Generic; System.Text; NBear.Common.Design;

namespace NGuestBook.NBearEntityDesign { public interface TAdmin : Entity { [PrimaryKey] int ID { get; } [SqlType("nvarchar(20)")] string Name { get; set; } [SqlType("nvarchar(50)")] string Password { get; set; } } public interface TComment : Entity { [PrimaryKey] int ID { get; } [SqlType("ntext")] string Content { get; set; } DateTime Time { get; set; } int MessageID { get; set; } }

52


基于.NET 平台的分层架构实战

果果红工作室

public interface TMessage : Entity { [PrimaryKey] int ID { get; } [SqlType("nvarchar(20)")] string GuestName { get; set; } [SqlType("nvarchar(100)")] string GuestEmail { get; set; } [SqlType("ntext")] string Content { get; set; } DateTime Time { get; set; } [SqlType("ntext")] string Reply { get; set; } [SqlType("nvarchar(10)")] string IsPass { get; set; } } }

设计完后,将这个工程编译备用。

2.创建 NBear 专用实体类及配置文件 在 NBear 文件包的 dist 目录下,有一个 NBear.Tools.EntityDesignToEntity.exe 程序, 打开它,点击“Browse”按钮,选择刚才编译生成的 NGuestBook.NBearEntityDesign.dll 文 件 , 并 在 Output Namespace 文 本 框 里 输 入 相 应 的 命 名 空 间 , 这 里 我 们 应 输 入 “NGuestBook.NBearDAL”。然后点击“Generate Entities”按钮,这时会在底下的文 本框里生成 NBear 专用实体类代码。 在解决方案下新建一个工程 NBearDAL,用于存放所有 ORM 数据访问层的实现代码。 在这个工程下新建 Entities.cs,将刚才自动生成的代码覆盖掉这个文件的代码,专用实体类 就做好了。 另外,需要给工程 NBearDAL 添加到 NBear.Common.dll 和 NBear.Data.dll 的引用, 这两个文件都在 dist 目录下。 点击“Generate Configuration”按钮,这时会生成配置代码。在 Web 工程下新建文 件 NBearConfig.xml,将生成的代码复制到这个文件里保存。 最后还要修改一下 Web.config 文件。增加如下配置代码: <configSections> <section name="entityConfig" type="NBear.Common.EntityConfigurationSection, NBear.Common"/> </configSections> <entityConfig> <includes> <add key="Sample Entity Config" value="~/NBearConfig.xml"/> </includes> </entityConfig>

然后再在<connectionStrings>节点下增加如下项:

53


基于.NET 平台的分层架构实战

果果红工作室

<add name="NBearConnectionString" connectionString="Server=LOCALHOST\SQLEXPRESS;Database=NGuestBook;Uid=WebUser;Pwd=1 23456" providerName="NBear.Data.SqlServer.SqlDbProvider"/>

其中 connectionString 是连接字符串,根据个人不同情况进行修改。这里使用的是 SQLServer2005。 因为数据库在上一篇中已经创建好了,这里就不需要创建数据库了。

3.编写转换器 这里出现了一个矛盾:业务逻辑层和表示层需要使用通用的实体类,如 AdminInfo, 而 NBear 需要使用专用实体类。怎么解决这个矛盾呢?我这里使用的方法是一个我称之为 “转换器”的方法。 即为没一个实体写一个专门的转换器,实现两种实体类的转换。这里 以管理员实体为例,这个转换器写在 NBearDAL 工程下的 AdminConvertor.cs 文件中。具 体代码如下:

AdminConvertor.cs using System; using NGuestBook.Entity; namespace NGuestBook.NBearDAL { /// /// 实体类转换器-管理员 /// public sealed class AdminConvertor { /// /// 由普通管理员实体类转化为NBear专用管理员实体类 /// /// 普通实体类 /// NBear专用实体类 public static TAdmin CommonEntityToNBearEntity(AdminInfo commonEntity) { TAdmin nbaerEntity = new TAdmin(); nbaerEntity.ID = commonEntity.ID; nbaerEntity.Name = commonEntity.Name; nbaerEntity.Password = commonEntity.Password; return nbaerEntity; } /// /// 由NBear专用管理员实体类转化为普通管理员实体类 /// /// NBear专用实体类 /// 普通实体类 public static AdminInfo NBearEntityToCommonEntity(TAdmin nbearEntity) { AdminInfo commonEntity = new AdminInfo();

54


基于.NET 平台的分层架构实战

果果红工作室

commonEntity.ID = nbearEntity.ID; commonEntity.Name = nbearEntity.Name; commonEntity.Password = nbearEntity.Password; return commonEntity; } } }

4.实现数据访问层 做完上述工作,我们就可以来实现数据访问层了。借助于 NBear 框架的支持,我们可 以非常方便的使用 ORM 方式访问数据库。关于 NBear 的细节,这里不再赘述,以管理员 为例,具体代码如下:

AdminDAL.cs using using using using using using using using

System; System.Collections.Generic; System.Text; System.Data.Common; NGuestBook.IDAL; NGuestBook.Entity; NBear.Common; NBear.Data;

namespace NGuestBook.NBearDAL { public class AdminDAL : IAdminDAL { /// /// 插入管理员 /// /// 管理员实体类 /// 是否成功 public bool Insert(AdminInfo admin) { Gateway.SetDefaultDatabase("NBearConnectionString"); DbTransaction transcation = Gateway.Default.BeginTransaction(); try { Gateway.Default.Save<TAdmin>(AdminConvertor.CommonEntityToNBearEntity(admin)); transcation.Commit(); return true; } catch { transcation.Rollback(); return false; } finally {

55


基于.NET 平台的分层架构实战

果果红工作室

Gateway.Default.CloseTransaction(transcation); } } /// /// 删除管理员 /// /// 欲删除的管理员的ID /// 是否成功 public bool Delete(int id) { Gateway.SetDefaultDatabase("NBearConnectionString"); DbTransaction transcation = Gateway.Default.BeginTransaction(); try { Gateway.Default.Delete<TAdmin>(id); transcation.Commit(); return true; } catch { transcation.Rollback(); return false; } finally { Gateway.Default.CloseTransaction(transcation); } } /// /// 更新管理员信息 /// /// 管理员实体类 /// 是否成功 public bool Update(AdminInfo admin) { Gateway.SetDefaultDatabase("NBearConnectionString"); DbTransaction transcation = Gateway.Default.BeginTransaction(); PropertyItem[] properties = { new PropertyItem("Name"), new PropertyItem("Password") }; object[] values ={ admin.Name, admin.Password }; try { Gateway.Default.Update<TAdmin>(properties, values, null, transcation); transcation.Commit(); return true;

56


基于.NET 平台的分层架构实战

果果红工作室

} catch { transcation.Rollback(); return false; } finally { Gateway.Default.CloseTransaction(transcation); } } /// /// 按ID取得管理员信息 /// /// 管理员ID /// 管理员实体类 public AdminInfo GetByID(int id) { Gateway.SetDefaultDatabase("NBearConnectionString"); TAdmin tAdmin = Gateway.Default.Find<TAdmin>(TAdmin._.ID == id); return tAdmin == null ? null : AdminConvertor.NBearEntityToCommonEntity(tAdmin); } /// /// 按用户名及密码取得管理员信息 /// /// 用户名 /// 密码 /// 管理员实体类,不存在时返回null public AdminInfo GetByNameAndPassword(string name, string password) { Gateway.SetDefaultDatabase("NBearConnectionString"); TAdmin tAdmin = Gateway.Default.Find<TAdmin>(TAdmin._.Name == name && TAdmin._.Password == password); return tAdmin == null ? null : AdminConvertor.NBearEntityToCommonEntity(tAdmin); } /// /// 按管理员名取得管理员信息 /// /// 管理员名 /// 管理员实体类 public AdminInfo GetByName(string name) { Gateway.SetDefaultDatabase("NBearConnectionString"); TAdmin tAdmin = Gateway.Default.Find<TAdmin>(TAdmin._.Name == name); return tAdmin == null ? null : AdminConvertor.NBearEntityToCommonEntity(tAdmin); }

57


基于.NET 平台的分层架构实战

果果红工作室

/// /// 取得全部管理员信息 /// /// 管理员实体类集合 public IList<AdminInfo> GetAll() { IList<AdminInfo> adminCollection = new List<AdminInfo>(); Gateway.SetDefaultDatabase("NBearConnectionString"); TAdmin[] tAdminCollection = Gateway.Default.FindArray<TAdmin>(null, TAdmin._.ID.Desc); foreach (TAdmin tAdmin in tAdminCollection) { adminCollection.Add(AdminConvertor.NBearEntityToCommonEntity(tAdmin)); } return adminCollection; } } }

十一、 业务逻辑层的实现 在这一篇文章中,将实现一个 NGuestBook 的业务逻辑层。 在实际应用中,业务逻辑层是至关重要的,他承载着整个系统最核心的部分,也是客户 最关注的部分。这一部分的实现,通常需要技术专家和领域专家通力合作。当然,在本文章 系列的 Demo 中,由于业务逻辑的简单性,这里看的可能还不是很明显。 在本篇文章的业务逻辑层实现中,业务逻辑层主要承担了以下职责: 1.对不同数据访问层的封装。使得表示层可以不关心具体的数据访问层。 2.业务逻辑数据的填充与转换。如管理员口令的加密。 3.核心业务的实现。这里很多业务逻辑只有一行代码,即一个业务逻辑方法恰好对应一 个数据访问方法,但是也有通过多个数据访问方法实现业务的。如 AdminBLL 中的 ChangePassword 方法就调用了 AdminDAL 的 GetByID 和 Update 两个方法。另外,虽 然许多方法只调用一个数据访问方法,但是从命名看也能看出两者着眼点的不同。如 AdminDAL 中的 GetByNameAndPassword,这个名字显然是从数据库的角度看问题—— 指按照指定的 Name 和 Password 两个字段的值取出相应信息,至于这样做的业务意义它 不需要知道。而 AdminBLL 中,调用它的方法叫 Login,这是从业务角度看问题——即这 个方法是管理员登录。 下面分步骤实现业务逻辑层:

1.建立工程 在这个架构中,业务逻辑层是可以替换的。及业务逻辑层不是直接耦合于表示层,而是 通过依赖注入机制实现。所以,我们这里将这个业务逻辑层不直接命名为 BLL,而是新建一 个叫 SimpleBLL 的工程,放置我们这个业务逻辑层的相关代码。

2.配置依赖注入

58


基于.NET 平台的分层架构实战

果果红工作室

业务逻辑层要通过反射工厂加载相应的数据访问层,这样就需要在 Web.config 中配置 需要使用的数据访问层。打开 Web.config,找到 appSettings 节点下的“DAL”项,将其 中的 value 赋予我们要使用的数据访问层工程名称,例如:要使用 NBearDAL,则这一项 应该这样写: <add key="DAL" value="NBearDAL"/>

3.编写散列加密工具类 因为在业务逻辑层的多处需要用到散列加密,所以在 Utility 工程下写一个辅助类 Encryptor,完成这个工作,这个辅助类的具体代码如下:

Encryptor.cs using System; using System.Collections.Generic; using System.Text; namespace NGuestBook.Utility { /// /// 辅助类-用于对敏感数据进行Hash散列,达到加密效果 /// public sealed class Encryptor { /// /// 使用MD5算法求Hash散列 /// /// 明文 /// 散列值 public static string MD5Encrypt(string text) { return System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(text, "MD5"); } /// /// 使用SHA1算法求Hash散列 /// /// 明文 /// 散列值 public static string SHA1Encrypt(string text) { return System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(text, "SHA1"); } } }

4.实现业务逻辑层

59


基于.NET 平台的分层架构实战

果果红工作室

有了上述准备工作和以前实现的组件,业务逻辑层的实现非常直观。这里仅以管理员为 例,展示如何实现业务逻辑层。 AdminBLL 类建立在 SimpleBLL 工程下的 AdminBLL.cs 文件中,实现了 IAdminBLL 接口,需具体代码如下:

AdminBLL.cs using using using using using using using using

System; System.Collections.Generic; System.Text; NGuestBook.Entity; NGuestBook.Factory; NGuestBook.IBLL; NGuestBook.IDAL; NGuestBook.Utility;

namespace NGuestBook.IBLL { /// /// 业务逻辑层接口-管理员 /// public class AdminBLL : IAdminBLL { /// /// 添加管理员 /// /// 新管理员实体类 /// 是否成功 public bool Add(AdminInfo admin) { admin.Password = Encryptor.MD5Encrypt(admin.Password); return DALFactory.CreateAdminDAL().Insert(admin); } /// /// 删除管理员 /// /// 欲删除的管理员的ID /// 是否成功 public bool Remove(int id) { return DALFactory.CreateAdminDAL().Delete(id); } /// /// 修改管理员密码 /// /// 欲修改密码的管理员的ID /// 新密码 /// 是否成功 public bool ChangePassword(int id, string password) {

60


基于.NET 平台的分层架构实战

果果红工作室

password = Encryptor.MD5Encrypt(password); AdminInfo admin = DALFactory.CreateAdminDAL().GetByID(id); admin.Password = password; return DALFactory.CreateAdminDAL().Update(admin); } /// /// 管理员登录 /// /// 管理员登录名 /// 管理员密码 /// 如果登录成功,则返回相应管理员的实体类,否则返回null public AdminInfo Login(string name, string password) { password = Encryptor.MD5Encrypt(password); return DALFactory.CreateAdminDAL().GetByNameAndPassword(name, password); } /// /// 取得全部管理员信息 /// /// 管理员实体类集合 public IList<AdminInfo> GetAll() { return DALFactory.CreateAdminDAL().GetAll(); } } }

十二、 表示层的实现 在这篇文章中,将讨论一下表示层的实现方法。 表示层是一个系统的“门脸”,不论你的系统设计的多么优秀,代码多么漂亮,系统的 可扩展性多么高,但是最终用户接触到的大多是表示层的东西。所以,表示层的优劣对于用 户最终对系统的评价至关重要。一般来说,表示层的优劣有一下两个评价指标: 1.美观。即外观设计漂亮,能给人美的感觉。 2.易用。即具有良好的用户体验,用户用起来舒服、顺手。 表示层的设计牵扯到很多非技术性问题,如美工、用户心理学等问题,但是在这篇文章 中,将不过多涉及这些问题,一来是我的水平有限,二来是这些内容和本系列文章的关系不 是很密切。这里将主要从技术实现的角度讨论表示层的设计。 一般来说,表示层的职责有以下两点: 1.接受用户的输入。 2.向用户呈现信息。 总体来说,就是与用户的交互。

61


基于.NET 平台的分层架构实战

果果红工作室

而表示层的实现技术也是多种多样的,如 C/S 架构下一般使用 Windows 窗体技术(甚 至是命令行窗体),而 B/S 架构下主要是使用 Web 页的形式实现。而且在 Ajax 技术出现以 后,又分出了同步模型的 B/S 架构实现和异步模型的 B/S 架构实现。在这篇文章中,将主 要讨论同步模型下 B/S 架构的表示层实现,而基于 Ajax 技术的异步模型将在下一篇中讨论。 另外,提到表示层的实现,大家一定会想到 MVC 这个词,不错 MVC 已经成为表示层 设计的经典模式。J2EE 平台上的 Struts 和最近微软推出的 ASP.NET MVC 都是实现 MVC 模式的框架。但是为了突出本系列文章的的重点——分层,而且也为了照顾初学者。这里 将不设计 MVC 模式,而是用传统的 ASP.NET 编程模型来完成表示层的设计。 一下的所有讨论,将围绕“管理员登录”这个用例展开。下面我们来逐步实现管理员登 录的表示层设计。

1.设计界面 为实现这个功能,我们首先要有一个 Web 页面。设计好的页面如下图所示:(时间所 迫,制作过于简陋,各位见谅)

首先,我们要在“Web”工程下建立一个新的 aspx 文件,叫做 Login.aspx,这就是 管理员登录的 Web 页面。完成后这个文件的代码如下:

Login.aspx: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherit s="Login" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w 3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>NGuestBook-管理员登录</title>

62


基于.NET 平台的分层架构实战

果果红工作室

<link href="Styles/Common.css" rel="stylesheet" type="text/css" /> <link href="Styles/Login.aspx.css" rel="stylesheet" type="text/css" /> </head> <body> <form id="form1" runat="server"> <div id="container"> <div id="box"> <h1>NGuestBook 管理员登录</h1> <table id="forms" cellpadding="0" cellspacing="0"> <tr> <td>用户名:</td> <td><asp:TextBox ID="Name" TextMode="SingleLine" runat="ser ver"></asp:TextBox></td> </tr> <tr> <td>密    码:</td> <td><asp:TextBox ID="Password" TextMode="Password" runat="s erver"></asp:TextBox></td> </tr> </table> <div id="buttons"> <asp:Button ID="Submit" runat="server" Text="登录 " OnClick="Submit_Click" /> <asp:Button ID="Cancel" runat="server" Text="重填 " OnClick="Cancel_Click" /> </div> </div> </div> </form> </body> </html>

可以看到,在这个文件里,主要是各种页面元素的结构,但是他们的外观却没有定义。 我们注意到,在这个文件的开头引用了两个外部的 CSS 文件,那里才是定义外观的地方。 其中 Common.css 是全局通用外观,而 Login.aspx.css 是这个页面的专用外观。

63


基于.NET 平台的分层架构实战

果果红工作室

这是我提倡的一种表示层设计的方法。即将结构与表现分离,其思想很类似目前 的“标准化网页设计”(有人称之为 DIV+CSS 布局),其核心思想是一样的,只不过我这 里没那么严格,并且适当的地方可以考虑使用 Table 布局。我的一般方法是这样的:aspx 文件中只存储页面结构,不存在任何与外观有关的代码。在工程中可以有一个专门的文件夹 放置 CSS 文件,除了通用样式外,每个文件有与自己同名的 CSS 文件,如 Login.asxp 配 套的就是 Login.aspx.css,这样,就可以使得结构与表现相分离。 在我的工程中,有一个 Styles 文件夹,专门存放 CSS 文件。下面把 Common.css 和 Login.aspx.css 文件的代码附上:

Common.css *{} { margin:0; padding:0; } h1,h2,h3,h4,h5,h6{} { font-size:12px; } #container{} { width:100%; }

Login.aspx.css #box{ width:40%; margin:200px auto auto auto; border:3px solid #036; } h1{ margin:1px; padding:5px 0; font-size:14px; color:#FFF; background:#036; text-align:center; } #forms{ margin:20px auto; font-size:12px; color:#036;

64


基于.NET 平台的分层架构实战

果果红工作室

} #forms input{ margin:10px; border:0; border-bottom:1px solid #036; width:160px; } #buttons{ margin-bottom:20px; text-align:center; } #buttons input{ margin:0 10px; }

2.表示逻辑 页面搞定了,但是要想页面发挥作用,还要有表示逻辑才行。我们登录的表示逻辑是这 样的:首先根据用户输入的用户名和密码,检查是否是系统管理员(系统管理员只有一个, 其用户名和密码定义在 Web.config 中,系统管理员可以添加、修改和删除普通管理员) , 如果是,则在 Session 中存储相应信息,并以系统管理员的身份登录后台管理页面。如果 不是,则检查是否是普通管理员,如果是,则将此管理员的信息存储到 Session 中,以普 通管理员身份返回主页。如果不是,则显示登录失败的提示。 当然,这里身份的控制还需要在后台页面和主页那边有 Session 检查才能完成。例如, 当请求后台页时,要检查 Session 中相应信息是否完整,如果不完整则是非法请求,不允 许访问此页面。而在主页也是,如果 Session 相应项中有普通管理员的信息,表明当前用 户是管理员,要显示修改、回复、删除等按钮,否则是游客,则不显示这些按钮。 我们首先要配置系统管理员的用户名和密码,打开 Web.config,在<appSettings>节 点下添加如下项: <add key="AdministartorName" value="admin"/> <add key="AdministartorPassword" value="123456"/>

这里很明显,前一个是系统管理员的用户名,这里设为“admin”,而后一项为密码, 设为“123456” 由于一般情况下 Web.config 是不允许请求的,所以这里不用担心密码泄露。 普通管理员登录的业务已经在业务逻辑层实现了,表示层可以直接调用。而系统管理员 的判断、Session 的操作检查及页面跳转都放在表示层里。而这一套逻辑,都放在“Submit” 这个 button 控件的 Click 事件中,具体代码参考如下:

Login.aspx.cs: using System;

65


基于.NET 平台的分层架构实战

using using using using using using using using using using using using

果果红工作室

System.Data; System.Configuration; System.Collections; System.Web; System.Web.Security; System.Web.UI; System.Web.UI.WebControls; System.Web.UI.WebControls.WebParts; System.Web.UI.HtmlControls; NGuestBook.Entity; NGuestBook.IBLL; NGuestBook.Factory;

public partial class Login : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void Cancel_Click(object sender, EventArgs e) { this.Name.Text = ""; this.Password.Text = ""; } protected void Submit_Click(object sender, EventArgs e) { string administratorName = ConfigurationManager.AppSettings["AdministartorName"]; string administratorPassword = ConfigurationManager.AppSettings["AdministartorPassword"]; //如果是系统管理员,则以系统管理员身份登录到后台 if (this.Name.Text == administratorName && this.Password.Text == administratorPassword) { Session["Administrator"] = "Administrator"; Response.Redirect("~/Manage.aspx"); return; } //判断是否为普通管理员,如果是,则以管理员身份登录到留言本,否则显示登录失败 AdminInfo admin = BLLFactory.CreateAdminBLL().Login(this.Name.Text, this.Password.Text); if (admin != null) { Session["Admin"] = admin; Response.Redirect("~/Default.aspx"); } else { Response.Redirect("~/Error.aspx?errMsg=登录失败"); } }

66


基于.NET 平台的分层架构实战

果果红工作室

}

67


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.