http://www.cnblogs.com/leoo2sk/archive/2008/06/16/1223312.html

基于.NET平台的分层架构实战(一)——综述

通过浏览博客园的文章发现,很多朋友对分层架构特别感兴趣,刚好我刚做完的毕业设计就是专门研究.NET平台上分层架构的(题目叫“基于.NET平台的分层架构与设计模式应用研究”)。通过做这篇论文,我对分层架构有了一定的了解,所以,就萌发了想写一个文章系列,详述一下分层架构。然而,论文的理论性太强,不适合在网上发布,尤其不适合初学者理解,所以,我想在这个文章系列中,少讲理论,而是通过做一个完整的案例来讨论分层架构的基本方法,这样会直观很多。希望在这个文章系列的写作过程中,能和朋友们一起学习,一起进步。

为了让朋友们把主要精力放在理解分层架构而不是案例本身,我准备选择一个相对简单的留言本系统作为Demo,这个系统的名字就叫做NGuestBook。

初步计划将这个文章系列分为以下几篇:
1.综述
2.系统需求分析及数据库设计
3.架构概要设计
4.实体类的实现
5.接口的设计与实现
6.依赖注入及IoC的设计与实现
7.数据访问层的第一种实现——Access+动态生成SQL语言
8.数据访问层的第二种实现——SQLServer+存储过程
9.数据访问层的第三种实现——基于NBear框架的ORM实现
10.业务逻辑层的实现
11.表示层的实现

当然,以上只是初步计划,在写文章的过程中可能会根据具体情况适当调整,但是内容大体就是这些。

这个文章系列不会对所用到的技术进行详细讲解,具体请参考相关文献,阅读文章前最好能对以下技术有一个了解:
1.C#语言
2.ASP.NET
3.设计模式
4.关系数据库基础知识
5.软件架构基本原则与软件工程基础知识
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。

基于.NET平台的分层架构实战(二)——需求分析与数据库设计

在实际的项目中,需求分析和数据库的设计是很重要的一个环节,这个环节会直接影响项目的开发过程和质量。实际中,这个环节不但需要系统分析师、软件工程师等计算机方面的专家,还需要相关领域的领域专家参与才能完成。

但是,在这个文章系列中,所要使用的Demo仅仅是一个例子,而且其业务极为简单,因此,这里并不是真正的需求分析和数据库设计,而是将Demo的需求和数据库罗列至此,使朋友们对Demo有一个大体的了解,方便后续文章中开发过程的理解。

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

这个项目的用例图如下:

图2.1、NGuestBook的用例图

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

进一步,数据库各表的设计如下:

管理员表(TAdmin)
ID    int    管理员ID    NotNull    主键,自增
Name    varchar(20)    登录名    NotNull
Password    varchar(50)    登录密码    NotNull    使用MD5加密

留言表(TMessage)
ID    int    留言ID    NotNull    主键,自增
GuestName    varchar(20)    留言者用户名    NotNull
GuestEmail    varchar(100)    留言者E-mail    Null
Content    text    留言内容    NotNull
Time    datetime    发表留言时间    NotNull    
Reply    text    回复    Null

IsPass    varchar(10)    是否通过验证    NotNull

评论表(TComment)
ID    int    评论ID    NotNull    主键,自增
Content    text    评论内容    NotNull
Time    datetime    发表评论时间    NotNull
MessageID    int    所属留言的ID    外键

基于.NET平台的分层架构实战(三)——架构概要设计

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

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

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

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。

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

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

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

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

职责划分:
目前,在典型的三层架构中,对层次各自的职责划分并没有一个统一的规范,综合现有的成功实践和.NET平台的特殊性,在本文中将三层架构的职责划分如下:

数据访问层——负责与数据源的交互,即数据的插入、删除、修改以及从数据库中读出数据等操作。对数据的正确性和有效性不负责,对数据的用途不了解,不负担任何业务逻辑。

业务逻辑层——负责系统领域业务的处理,负责逻辑性数据的生成、处理及转换。对流入的逻辑性数据的正确性及有效性负责,对流出的逻辑性数据及用户性数据不负责,对数据的呈现样式不负责。

表示层——负责接收用户的输入、将输出呈现给用户以及访问安全性验证。对流入的数据的正确性和有效性负责,对呈现样式负责,对流出的数据正确性不负责,但负责在数据不正确时给出相应的异常信息。

模块划分及交互设计:
综合以上分析,可在宏观上将整个系统分为一下几个模块:
实体类模块——一组实体类的集合,负责整个系统中数据的封装及传递。
数据访问层接口族——一组接口的集合,表示数据访问层的接口。
业务逻辑层接口族——一组接口的集合,表示业务逻辑层的接口。
数据访问层模块——一组类的集合,完成数据访问层的具体功能,实现数据访问层接口族。
业务逻辑层模块——一组类的集合,完成业务逻辑层的具体功能,实现业务逻辑层接口族。
表示层模块——程序及可视元素的集合,负责完成表示层的具体功能。
IoC容器模块——负责依赖注入的实现。
辅助类模块——完成全局辅助性功能。

各模块间交互关系如下:


图3.1、总体架构图

基于.NET平台的分层架构实战(四)——实体类的设计与实现

实体类是现实实体在计算机中的表示。它贯穿于整个架构,负担着在各层次及模块间传递数据的职责。一般来说,实体类可以分为“贫血实体类”和“充血实体类”,前者仅仅保存实体的属性,而后者还包含一些实体间的关系与逻辑。我们在这个Demo中用的实体类将是“贫血实体类”。

大多情况下,实体类和数据库中的表(这里指实体表,不包括表示多对多对应的关系表)是一一对应的,但这并不是一个限制,在复杂的数据库设计中,有可能出现一个实体类对应多个表,或者交叉对应的情况。在本文的Demo中,实体类和表是一一对应的,并且实体类中的属性和表中的字段也是对应的。

在看实体类的代码前,先看一下系统的工程结构。

图4.1、工程结构图

如上图所示,在初始阶段,整个系统包括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
    {
        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; }
        }

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
{
    /// <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; }
        }
    }
}

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

基于.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:添加新管理员

经过整理,可得以下接口操作:
IAdminBLL:Add(添加管理员),Remove(删除管理员),ChangePassword(修改管理员密码),Login(管理员登录),GetAll(取得全部管理员)
IMessageBLL:Add(添加留言),Remove(删除留言),Revert(回复留言),Pass(将留言通过验证),GetByPage(按分页取得留言)
ICommentBLL:Add(添加评论),Remove(删除评论),GetByMessage(按留言取得全部评论)

这三个接口文件都放在IBLL工程下,具体代码如下:

IAdminBLL.cs:

using System;
using System.Collections.Generic;
using System.Text;
using 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);

/// <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>
        /// <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>
        /// 删除评论
        /// </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 System;
using System.Collections.Generic;
using System.Text;
using NGuestBook.Entity;

namespace NGuestBook.IDAL
{
    /// <summary>
    /// 数据访问层接口-管理员
    /// </summary>
    public interface IAdminDAL
    {
        /// <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>
        IList<AdminInfo> GetAll();
    }
}

IMessageDAL.cs:

using System;
using System.Collections.Generic;
using System.Text;
using 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>
        /// 按分页取得留言信息
        /// </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>
        IList<CommentInfo> GetByMessage(int messageId);
    }
}

基于.NET平台的分层架构实战(六)——依赖注入机制及IoC的设计与实现

我们设计的分层架构,层与层之间应该是松散耦合的。因为是单向单一调用,所以,这里的“松散耦合”实际是指上层类不能具体依赖于下层类,而应该依赖于下层提供的一个接口。这样,上层类不能直接实例化下层中的类,而只持有接口,至于接口所指变量最终究竟是哪一个类,则由依赖注入机制决定。

之所以这样做,是为了实现层与层之间的“可替换”式设计,例如,现在需要换一种方式实现数据访问层,只要这个实现遵循了前面定义的数据访问层接口,业务逻辑层和表示层不需要做任何改动,只需要改一下配置文件系统即可正常运行。另外,基于这种结构的系统,还可以实现并行开发。即不同开发人员可以专注于自己的层次,只有接口被定义好了,开发出来的东西就可以无缝连接。

在J2EE平台上,主要使用Spring框架实现依赖注入。这里,我们将自己做一个依赖注入容器。

依赖注入的理论基础是Abstract Factory设计模式,这里结合具体实例简单介绍一下。

图6.1、Abtract Factory应用示例

上图以数据访问层为例,展示了Abstract Factory模式的应用。如图,现假设有针对Access和SQLServer两种数据库的数据访问层,它们都实现了数据访问层接口。每个数据访问层有自己的工厂,所有工厂都实现自IDALFactory接口。而客户类(这里就是业务逻辑层类)仅与工厂接口、数据访问层接口耦合,而与具体类无关,这样,只要通过配置文件确定实例化哪个工厂,就可以得到不同的数据访问层。
 
然而,这种设计虽然可行,但是代码比较冗余,因为这样需要为数据访问层的每一个实现编写一个工厂,业务逻辑层也一样。在以前,我们毫无办法,但是,.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)
        {
            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 System;
using System.Configuration;
using System.Reflection;
using System.Web;
using System.Web.Caching;
using NGuestBook.Utility;

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

/// <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()
        {
            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");
        }
    }
}

基于.NET平台的分层架构实战(七)——数据访问层的第一种实现:Access+SQL

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

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

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

 1using System;
 2using System.Web;
 3using System.Web.Caching;
 4using System.Configuration;
 5using System.Data;
 6using System.Data.OleDb;
 7using NGuestBook.Utility;
 8
 9namespace NGuestBook.AccessDAL
10{
11    /// <summary>
12    /// Access数据库操作助手
13    /// </summary>
14    public sealed class AccessDALHelper
15    {
16        /// <summary>
17        /// 读取Access数据库的连接字符串
18        /// 首先从缓存里读取,如果不存在则到配置文件中读取,并放入缓存
19        /// </summary>
20        /// <returns>Access数据库的连接字符串</returns>
21        private static string GetConnectionString()
22        {
23            if (CacheAccess.GetFromCache("AccessConnectionString") != null)
24            {
25                return CacheAccess.GetFromCache("AccessConnectionString").ToString();
26            }
27            else
28            {
29                string dbPath = ConfigurationManager.AppSettings["AccessPath"];
30                string dbAbsolutePath = HttpContext.Current.Server.MapPath(dbPath);
31                string connectionString = ConfigurationManager.AppSettings["AccessConnectionString"];
32
33                CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config"));
34                CacheAccess.SaveToCache("AccessConnectionString", connectionString.Replace("{DBPath}" lt;/span>, dbAbsolutePath), fileDependency);
35
36                return connectionString.Replace("{DBPath}", dbAbsolutePath);
37            }
38        }
39
40        /// <summary>
41        /// 执行SQL语句并且不返回任何值
42        /// </summary>
43        /// <param name="SQLCommand">所执行的SQL命令</param>
44        /// <param name="parameters">参数集合</param>
45        public static void ExecuteSQLNonQuery(string SQLCommand,OleDbParameter[] parameters)
46        {
47            OleDbConnection connection = new OleDbConnection(GetConnectionString());
48            OleDbCommand command = new OleDbCommand(SQLCommand, connection);
49
50            for (int i = 0; i < parameters.Length; i++)
51            {
52                command.Parameters.Add(parameters[i]);
53            }
54
55            connection.Open();
56            command.ExecuteNonQuery();
57            connection.Close();
58        }
59
60        /// <summary>
61        /// 执行SQL语句并返回包含查询结果的DataReader
62        /// </summary>
63        /// <param name="SQLCommand">所执行的SQL命令</param>
64        /// <param name="parameters">参数集合</param>
65        /// <returns></returns>
66        public static OleDbDataReader ExecuteSQLDataReader(string SQLCommand,OleDbParameter[] parameters)
67        {
68            OleDbConnection connection = new OleDbConnection(GetConnectionString());
69            OleDbCommand command = new OleDbCommand(SQLCommand, connection);
70
71            for (int i = 0; i < parameters.Length; i++)
72            {
73                command.Parameters.Add(parameters[i]);
74            }
75
76            connection.Open();
77            OleDbDataReader dataReader = command.ExecuteReader();
78            //connection.Close();
79
80            return dataReader;
81        }
82    }
83}

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

AdminDAL:

  1using System;
  2using System.Collections.Generic;
  3using System.Text;
  4using System.Data;
  5using System.Data.OleDb;
  6using NGuestBook.IDAL;
  7using NGuestBook.Entity;
  8
  9namespace NGuestBook.AccessDAL
 10{
 11    public class AdminDAL : IAdminDAL
 12    {
 13        /// <summary>
 14        /// 插入管理员
 15        /// </summary>
 16        /// <param name="admin">管理员实体类</param>
 17        /// <returns>是否成功</returns>
 18        public bool Insert(AdminInfo admin)
 19        {
 20            string SQLCommand = "insert into [TAdmin]([Name],[Password]) values(@name,@password)";
 21            OleDbParameter[] parameters ={
 22                new OleDbParameter("name",admin.Name),
 23                new OleDbParameter("password",admin.Password)
 24            };
 25
 26            try
 27            {
 28                AccessDALHelper.ExecuteSQLNonQuery(SQLCommand, parameters);
 29                return true;
 30            }
 31            catch
 32            {
 33                return false;
 34            }
 35        }
 36
 37        /// <summary>
 38        /// 删除管理员
 39        /// </summary>
 40        /// <param name="id">欲删除的管理员的ID</param>
 41        /// <returns>是否成功</returns>
 42        public bool Delete(int id)
 43        {
 44            string SQLCommand = "delete from [TAdmin] where [ID]=@id";
 45            OleDbParameter[] parameters ={
 46                new OleDbParameter("id",id)
 47            };
 48
 49            try
 50            {
 51                AccessDALHelper.ExecuteSQLNonQuery(SQLCommand, parameters);
 52                return true;
 53            }
 54            catch
 55            {
 56                return false;
 57            }
 58        }
 59
 60        /// <summary>
 61        /// 更新管理员信息
 62        /// </summary>
 63        /// <param name="admin">管理员实体类</param>
 64        /// <returns>是否成功</returns>
 65        public bool Update(AdminInfo admin)
 66        {
 67            string SQLCommand = "update [TAdmin] set [Name]=@name,[Password]=@password where [ID]=@id";
 68            OleDbParameter[] parameters ={
 69                new OleDbParameter("id",admin.ID),
 70                new OleDbParameter("name",admin.Name),
 71                new OleDbParameter("password",admin.Password)
 72            };
 73
 74            try
 75            {
 76                AccessDALHelper.ExecuteSQLNonQuery(SQLCommand, parameters);
 77                return true;
 78            }
 79            catch
 80            {
 81                return false;
 82            }
 83        }
 84
 85        /// <summary>
 86        /// 按ID取得管理员信息
 87        /// </summary>
 88        /// <param name="id">管理员ID</param>
 89        /// <returns>管理员实体类</returns>
 90        public AdminInfo GetByID(int id)
 91        {
 92            string SQLCommand = "select * from [TAdmin] where [ID]=@id";
 93            OleDbParameter[] parameters ={
 94                new OleDbParameter("id",id)
 95            };
 96
 97            try
 98            {
 99                OleDbDataReader dataReader = AccessDALHelper.ExecuteSQLDataReader(SQLCommand, parameters);
100                if (!dataReader.HasRows)
101                {
102                    throw new Exception();
103                }
104
105                AdminInfo admin = new AdminInfo();
106                dataReader.Read();
107                admin.ID=(int)dataReader["ID"];
108                admin.Name=(string)dataReader["Name"];
109                admin.Password=(string)dataReader["Password"];
110
111                return admin;
112            }
113            catch
114            {
115                return null;
116            }
117        }
118
119        /// <summary>
120        /// 按用户名及密码取得管理员信息
121        /// </summary>
122        /// <param name="name">用户名</param>
123        /// <param name="password">密码</param>
124        /// <returns>管理员实体类,不存在时返回null</returns>
125        public AdminInfo GetByNameAndPassword(string name, string password)
126        {
127            string SQLCommand = "select * from [TAdmin] where [Name]=@name and [Password]=@password";
128            OleDbParameter[] parameters ={
129                new OleDbParameter("name",name),
130                new OleDbParameter("password",password),
131            };
132
133            try
134            {
135                OleDbDataReader dataReader = AccessDALHelper.ExecuteSQLDataReader(SQLCommand, parameters);
136                if (!dataReader.HasRows)
137                {
138                    throw new Exception();
139                }
140
141                AdminInfo admin = new AdminInfo();
142                dataReader.Read();
143                admin.ID = (int)dataReader["ID"];
144                admin.Name = (string)dataReader["Name"];
145                admin.Password = (string)dataReader["Password"];
146
147                return admin;
148            }
149            catch
150            {
151                return null;
152            }
153        }
154
155        /// <summary>
156        /// 按管理员名取得管理员信息
157        /// </summary>
158        /// <param name="name">管理员名</param>
159        /// <returns>管理员实体类</returns>
160        public AdminInfo GetByName(string name)
161        {
162            string SQLCommand = "select * from [TAdmin] where [Name]=@name";
163            OleDbParameter[] parameters ={
164                new OleDbParameter("name",name),
165            };
166
167            try
168            {
169                OleDbDataReader dataReader = AccessDALHelper.ExecuteSQLDataReader(SQLCommand, parameters);
170                if (!dataReader.HasRows)
171                {
172                    throw new Exception();
173                }
174
175                AdminInfo admin = new AdminInfo();
176                dataReader.Read();
177                admin.ID = (int)dataReader["ID"];
178                admin.Name = (string)dataReader["Name"];
179                admin.Password = (string)dataReader["Password"];
180
181                return admin;
182            }
183            catch
184            {
185                return null;
186            }
187        }
188
189        /// <summary>
190        /// 取得全部管理员信息
191        /// </summary>
192        /// <returns>管理员实体类集合</returns>
193        public IList<AdminInfo> GetAll()
194        {
195            string SQLCommand = "select * from [TAdmin]";
196            try
197            {
198                OleDbDataReader dataReader = AccessDALHelper.ExecuteSQLDataReader(SQLCommand, null);
199                if (!dataReader.HasRows)
200                {
201                    throw new Exception();
202                }
203
204                IList<AdminInfo> adminCollection = new List<AdminInfo>();
205                int i = 0;
206                while (dataReader.Read())
207                {
208                    AdminInfo admin = new AdminInfo();
209                    admin.ID = (int)dataReader["ID"];
210                    admin.Name = (string)dataReader["Name"];
211                    admin.Password = (string)dataReader["Password"];
212
213                    adminCollection.Add(admin);
214                    i++;
215                }
216
217                return adminCollection;
218            }
219            catch
220            {
221                return null;
222            }
223        }
224    }
225}

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

基于.NET平台的分层架构实战(八)——数据访问层的第二种实现:SQLServer+存储过程

在上一篇中,讨论了使用SQL构建数据访问层的方法,并且针对的是Access数据库。而这一篇中,将要创建一个针对SQLServer数据库的数据访问层,并且配合存储过程实现。

曾经有朋友问我使用SQL和存储过程在效率上的差别,惭愧的是我对这方面没有研究,也没有实际做过测试。通过查阅资料,发现在一般情况下,存储过程的效率由于使用SQL,但是也不绝对,也发现有的朋友测试时发现在特定情况下SQL的效率优于存储过程,所以这个问题不能一概而论。

好,废话不多说,这里先列出使用存储过程构建数据访问层的一般步骤:
1.创建新工程
2.创建数据库
3.编写相应存储过程
4.编写数据库辅助类
5.实现数据访问层

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

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

编写存储过程
数据库创建完成后,我们就要编写存储过程了。由于数据访问层接口已经确定,所以需要哪些存储过程也很好确定。例如数据访问层接口中有一个添加管理员方法,那么就一定有一个存储过程实现这个功能。
还是以管理员模块为例,经过简单分析,需要一下存储过程:

插入管理员记录
删除管理员记录
更新管理员信息
按ID取得管理员记录
按用户名及密码取得管理员记录
按用户名取得管理员记录
取得全部管理员记录

创建这些存储过程的SQL代码如下:

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:        <T2噬菌体>
-- Create date: <2008-07-04>
-- Description:    <插入管理员记录>
-- =============================================
CREATE PROCEDURE [dbo].[Pr_InsertAdmin] 
(
     @Name Nvarchar(20),
     @Password Nvarchar(50)
)
AS
INSERT INTO TAdmin
(
    [Name],
    [Password]
)
VALUES
(
    @Name,
    @Password
)
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
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
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
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
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
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:

 1using System;
 2using System.Collections.Generic;
 3using System.Configuration;
 4using System.Data;
 5using System.Data.SqlClient;
 6
 7namespace NGuestBook.SQLServerDAL
 8{
 9    /// <summary>
10    /// SQLServer数据库操作助手
11    /// </summary>
12    public sealed class SQLServerDALHelper
13    {
14        /// <summary>
15        /// 用于连接SQLServer数据库的连接字符串,存于Web.config中
16        /// </summary>
17        private static readonly string _sqlConnectionString = ConfigurationManager.AppSettings["SQLServerConnectionString"];
18
19        /// <summary>
20        /// 执行SQL命令,不返回任何值
21        /// </summary>
22        /// <param name="sql">SQL命令</param>
23        public static void ExecuteSQLNonQurey(string sql)
24        {
25            SqlConnection connection = new SqlConnection(_sqlConnectionString);
26            SqlCommand command = new SqlCommand(sql,connection);
27            connection.Open();
28            command.ExecuteNonQuery();
29            connection.Close();
30        }
31
32        /// <summary>
33        /// 执行SQL命令,并返回SqlDataReader
34        /// </summary>
35        /// <param name="sql">SQL命令</param>
36        /// <returns>包含查询结果的SqlDataReader</returns>
37        public static SqlDataReader ExecuteSQLReader(string sql)
38        {
39            SqlConnection connection = new SqlConnection(_sqlConnectionString);
40            SqlCommand command = new SqlCommand(sql, connection);
41            connection.Open();
42            SqlDataReader sqlReader = command.ExecuteReader();
43            //connection.Close();
44
45            return sqlReader;
46        }
47
48        /// <summary>
49        /// 执行存储过程,不返回任何值
50        /// </summary>
51        /// <param name="storedProcedureName">存储过程名</param>
52        /// <param name="parameters">参数</param>
53        public static void ExecuteProcedureNonQurey(string storedProcedureName,IDataParameter[] parameters)
54        {
55            SqlConnection connection = new SqlConnection(_sqlConnectionString);
56            SqlCommand command = new SqlCommand(storedProcedureName,connection);
57            command.CommandType = CommandType.StoredProcedure;
58            if (parameters != null)
59            {
60                foreach (SqlParameter parameter in parameters)
61                {
62                    command.Parameters.Add(parameter);
63                }
64            }
65            connection.Open();
66            command.ExecuteNonQuery();
67            connection.Close();
68        }
69
70        /// <summary>
71        /// 执行存储,并返回SqlDataReader
72        /// </summary>
73        /// <param name="storedProcedureName">存储过程名</param>
74        /// <param name="parameters">参数</param>
75        /// <returns>包含查询结果的SqlDataReader</returns>
76        public static SqlDataReader ExecuteProcedureReader(string storedProcedureName,IDataParameter[] parameters)
77        {
78            SqlConnection connection = new SqlConnection(_sqlConnectionString);
79            SqlCommand command = new SqlCommand(storedProcedureName,connection);
80            command.CommandType = CommandType.StoredProcedure;
81            if (parameters != null)
82            {
83                foreach (SqlParameter parameter in parameters)
84                {
85                    command.Parameters.Add(parameter);
86                }
87            }
88            connection.Open();
89            SqlDataReader sqlReader = command.ExecuteReader();
90            //connection.Close();
91
92            return sqlReader;
93        }
94    }
95}

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

AdminDAL.cs:

  1using System;
  2using System.Collections.Generic;
  3using System.Text;
  4using System.Data;
  5using System.Data.SqlClient;
  6using NGuestBook.IDAL;
  7using NGuestBook.Entity;
  8
  9namespace NGuestBook.SQLServerDAL
 10{
 11    public class AdminDAL : IAdminDAL
 12    {
 13        /// <summary>
 14        /// 插入管理员
 15        /// </summary>
 16        /// <param name="admin">管理员实体类</param>
 17        /// <returns>是否成功</returns>
 18        public bool Insert(AdminInfo admin)
 19        {
 20            SqlParameter[] parameters =
 21                {
 22                    new SqlParameter("@Name",SqlDbType.NVarChar),
 23                    new SqlParameter("@Password",SqlDbType.NVarChar)
 24                };
 25            parameters[0].Value = admin.Name;
 26            parameters[1].Value = admin.Password;
 27            try
 28            {
 29                SQLServerDALHelper.ExecuteProcedureNonQurey("Pr_InsertAdmin", parameters);
 30                return true;
 31            }
 32            catch
 33            {
 34                return false;
 35            }
 36        }
 37
 38        /// <summary>
 39        /// 删除管理员
 40        /// </summary>
 41        /// <param name="id">欲删除的管理员的ID</param>
 42        /// <returns>是否成功</returns>
 43        public bool Delete(int id)
 44        {
 45            SqlParameter[] parameters =
 46                {
 47                    new SqlParameter("@ID",SqlDbType.Int)
 48                };
 49            parameters[0].Value = id;
 50            try
 51            {
 52                SQLServerDALHelper.ExecuteProcedureNonQurey("Pr_DeleteAdmin", parameters);
 53                return true;
 54            }
 55            catch
 56            {
 57                return false;
 58            }
 59        }
 60
 61        ///<summary>
 62        /// 更新管理员信息
 63        /// </summary>
 64        /// <param name="admin">管理员实体类</param>
 65        /// <returns>是否成功</returns>
 66        public bool Update(AdminInfo admin)
 67        {
 68            SqlParameter[] parameters =
 69                {
 70                    new SqlParameter("@ID",SqlDbType.Int),
 71                    new SqlParameter("@Name",SqlDbType.NVarChar),
 72                    new SqlParameter("@Password",SqlDbType.NVarChar)
 73                };
 74            parameters[0].Value = admin.ID;
 75            parameters[1].Value = admin.Name;
 76            parameters[2].Value = admin.Password;
 77            try
 78            {
 79                SQLServerDALHelper.ExecuteProcedureNonQurey("Pr_UpdateAdmin", parameters);
 80                return true;
 81            }
 82            catch
 83            {
 84                return false;
 85            }
 86        }
 87
 88        /// <summary>
 89        /// 按ID取得管理员信息
 90        /// </summary>
 91        /// <param name="id">管理员ID</param>
 92        /// <returns>管理员实体类</returns>
 93        public AdminInfo GetByID(int id)
 94        {
 95            SqlParameter[] parameters =
 96                {
 97                    new SqlParameter("@ID",SqlDbType.Int)
 98                };
 99            parameters[0].Value = id;
100            SqlDataReader dataReader = null;
101            try
102            {
103                dataReader = SQLServerDALHelper.ExecuteProcedureReader("GetAdminByID", parameters);
104                dataReader.Read();
105                AdminInfo admin = new AdminInfo();
106                admin.ID = (int)dataReader["ID"];
107                admin.Name = (string)dataReader["Name"];
108                admin.Password = (string)dataReader["Password"];
109
110                return admin;
111            }
112            catch
113            {
114                return null;
115            }
116            finally
117            {
118                dataReader.Close();
119            }
120        }
121
122        /// <summary>
123        /// 按用户名及密码取得管理员信息
124        /// </summary>
125        /// <param name="name">用户名</param>
126        /// <param name="password">密码</param>
127        /// <returns>管理员实体类,不存在时返回null</returns>
128        public AdminInfo GetByNameAndPassword(string name, string password)
129        {
130            SqlParameter[] parameters =
131                {
132                    new SqlParameter("@Name",SqlDbType.NVarChar),
133                    new SqlParameter("@Password",SqlDbType.NVarChar)
134                };
135            parameters[0].Value = name;
136            parameters[1].Value = password;
137            SqlDataReader dataReader = null;
138            try
139            {
140                dataReader = SQLServerDALHelper.ExecuteProcedureReader("GetAdminByNameAndPassword", parameters);
141                dataReader.Read();
142                AdminInfo admin = new AdminInfo();
143                admin.ID = (int)dataReader["ID"];
144                admin.Name = (string)dataReader["Name"];
145                admin.Password = (string)dataReader["Password"];
146
147                return admin;
148            }
149            catch
150            {
151                return null;
152            }
153            finally
154            {
155                dataReader.Close();
156            }
157        }
158
159        /// <summary>
160        /// 按管理员名取得管理员信息
161        /// </summary>
162        /// <param name="name">管理员名</param>
163        /// <returns>管理员实体类</returns>
164        public AdminInfo GetByName(string name)
165        {
166            SqlParameter[] parameters =
167                {
168                    new SqlParameter("@Name",SqlDbType.NVarChar)
169                };
170            parameters[0].Value = name;
171            SqlDataReader dataReader = null;
172            try
173            {
174                dataReader = SQLServerDALHelper.ExecuteProcedureReader("GetAdminByName", parameters);
175                dataReader.Read();
176                AdminInfo admin = new AdminInfo();
177                admin.ID = (int)dataReader["ID"];
178                admin.Name = (string)dataReader["Name"];
179                admin.Password = (string)dataReader["Password"];
180
181                return admin;
182            }
183            catch
184            {
185                return null;
186            }
187            finally
188            {
189                dataReader.Close();
190            }
191        }
192
193        /// <summary>
194        /// 取得全部管理员信息
195        /// </summary>
196        /// <returns>管理员实体类集合</returns>
197        public IList<AdminInfo> GetAll()
198        {
199            SqlDataReader dataReader = null;
200            try
201            {
202                dataReader = SQLServerDALHelper.ExecuteProcedureReader("GetAllAdmin", null);
203                IList<AdminInfo> adminCollection=new List<AdminInfo>();
204                while (dataReader.Read())
205                {
206                    AdminInfo admin = new AdminInfo();
207                    admin.ID = (int)dataReader["ID"];
208                    admin.Name = (string)dataReader["Name"];
209                    admin.Password = (string)dataReader["Password"];
210                    adminCollection.Add(admin);
211                }
212
213                return adminCollection;
214            }
215            catch
216            {
217                return null;
218            }
219            finally
220            {
221                dataReader.Close();
222            }
223        }
224    }
225}

基于.NET平台的分层架构实战(九)——数据访问层的第三种实现:基于NBear框架的ORM实现

前面的文章讨论了使用SQL语句和存储过程两种数据访问层的实现方式,这一篇里,将讨论使用ORM方式实现数据访问层的方法。

对象-关系映射(Object/Relation Mapping,简称ORM),是随着面向对象的软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射(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:

 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4using NBear.Common.Design;
 5
 6namespace NGuestBook.NBearEntityDesign
 7{
 8    public interface TAdmin : Entity
 9    {
10        [PrimaryKey]
11        int ID { get; }
12        [SqlType("nvarchar(20)")]
13        string Name { get; set; }
14        [SqlType("nvarchar(50)")]
15        string Password { get; set; }
16    }
17
18    public interface TComment : Entity
19    {
20        [PrimaryKey]
21        int ID { get; }
22        [SqlType("ntext")]
23        string Content { get; set; }
24        DateTime Time { get; set; }
25        int MessageID { get; set; }
26    }
27
28    public interface TMessage : Entity
29    {
30        [PrimaryKey]
31        int ID { get; }
32        [SqlType("nvarchar(20)")]
33        string GuestName { get; set; }
34        [SqlType("nvarchar(100)")]
35        string GuestEmail { get; set; }
36        [SqlType("ntext")]
37        string Content { get; set; }
38        DateTime Time { get; set; }
39        [SqlType("ntext")]
40        string Reply { get; set; }
41        [SqlType("nvarchar(10)")]
42        string IsPass { get; set; }
43    }
44}

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

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>节点下增加如下项:
<add name="NBearConnectionString" connectionString="Server=LOCALHOST\SQLEXPRESS;Database=NGuestBook;Uid=WebUser;Pwd=123456" providerName="NBear.Data.SqlServer.SqlDbProvider"/>
其中connectionString是连接字符串,根据个人不同情况进行修改。这里使用的是SQLServer2005。
因为数据库在上一篇中已经创建好了,这里就不需要创建数据库了。

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

AdminConvertor.cs:

 1using System;
 2using NGuestBook.Entity;
 3
 4namespace NGuestBook.NBearDAL
 5{
 6    /// <summary>
 7    /// 实体类转换器-管理员
 8    /// </summary>
 9    public sealed class AdminConvertor
10    {
11        /// <summary>
12        /// 由普通管理员实体类转化为NBear专用管理员实体类
13        /// </summary>
14        /// <param name="commonEntity">普通实体类</param>
15        /// <returns>NBear专用实体类</returns>
16        public static TAdmin CommonEntityToNBearEntity(AdminInfo commonEntity)
17        {
18            TAdmin nbaerEntity = new TAdmin();
19            nbaerEntity.ID = commonEntity.ID;
20            nbaerEntity.Name = commonEntity.Name;
21            nbaerEntity.Password = commonEntity.Password;
22
23            return nbaerEntity;
24        }
25
26        /// <summary>
27        /// 由NBear专用管理员实体类转化为普通管理员实体类
28        /// </summary>
29        /// <param name="nbearEntity">NBear专用实体类</param>
30        /// <returns>普通实体类</returns>
31        public static AdminInfo NBearEntityToCommonEntity(TAdmin nbearEntity)
32        {
33            AdminInfo commonEntity = new AdminInfo();
34            commonEntity.ID = nbearEntity.ID;
35            commonEntity.Name = nbearEntity.Name;
36            commonEntity.Password = nbearEntity.Password;
37
38            return commonEntity;
39        }
40    }
41}

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

AdminDAL.cs:

  1using System;
  2using System.Collections.Generic;
  3using System.Text;
  4using System.Data.Common;
  5using NGuestBook.IDAL;
  6using NGuestBook.Entity;
  7using NBear.Common;
  8using NBear.Data;
  9
 10namespace NGuestBook.NBearDAL
 11{
 12    public class AdminDAL : IAdminDAL
 13    {
 14        /// <summary>
 15        /// 插入管理员
 16        /// </summary>
 17        /// <param name="admin">管理员实体类</param>
 18        /// <returns>是否成功</returns>
 19        public bool Insert(AdminInfo admin)
 20        {
 21            Gateway.SetDefaultDatabase("NBearConnectionString");
 22            DbTransaction transcation = Gateway.Default.BeginTransaction();
 23            try
 24            {
 25                Gateway.Default.Save<TAdmin>(AdminConvertor.CommonEntityToNBearEntity(admin));
 26                transcation.Commit();
 27                return true;
 28            }
 29            catch
 30            {
 31                transcation.Rollback();
 32                return false;
 33            }
 34            finally
 35            {
 36                Gateway.Default.CloseTransaction(transcation);
 37            }
 38        }
 39
 40        /// <summary>
 41        /// 删除管理员
 42        /// </summary>
 43        /// <param name="id">欲删除的管理员的ID</param>
 44        /// <returns>是否成功</returns>
 45        public bool Delete(int id)
 46        {
 47            Gateway.SetDefaultDatabase("NBearConnectionString");
 48            DbTransaction transcation = Gateway.Default.BeginTransaction();
 49            try
 50            {
 51                Gateway.Default.Delete<TAdmin>(id);
 52                transcation.Commit();
 53                return true;
 54            }
 55            catch
 56            {
 57                transcation.Rollback();
 58                return false;
 59            }
 60            finally
 61            {
 62                Gateway.Default.CloseTransaction(transcation);
 63            }
 64        }
 65
 66        /// <summary>
 67        /// 更新管理员信息
 68        /// </summary>
 69        /// <param name="admin">管理员实体类</param>
 70        /// <returns>是否成功</returns>
 71        public bool Update(AdminInfo admin)
 72        {
 73            Gateway.SetDefaultDatabase("NBearConnectionString");
 74            DbTransaction transcation = Gateway.Default.BeginTransaction();
 75            PropertyItem[] properties = {
 76                new PropertyItem("Name"),
 77                new PropertyItem("Password")
 78            };
 79            object[] values ={
 80                admin.Name,
 81                admin.Password
 82            };
 83            try
 84            {
 85                Gateway.Default.Update<TAdmin>(properties, values, null, transcation);
 86                transcation.Commit();
 87                return true;
 88            }
 89            catch
 90            {
 91                transcation.Rollback();
 92                return false;
 93            }
 94            finally
 95            {
 96                Gateway.Default.CloseTransaction(transcation);
 97            }
 98        }
 99
100        /// <summary>
101        /// 按ID取得管理员信息
102        /// </summary>
103        /// <param name="id">管理员ID</param>
104        /// <returns>管理员实体类</returns>
105        public AdminInfo GetByID(int id)
106        {
107            Gateway.SetDefaultDatabase("NBearConnectionString");
108            TAdmin tAdmin = Gateway.Default.Find<TAdmin>(TAdmin._.ID == id);
109            return tAdmin == null ? null : AdminConvertor.NBearEntityToCommonEntity(tAdmin);
110        }
111
112        /// <summary>
113        /// 按用户名及密码取得管理员信息
114        /// </summary>
115        /// <param name="name">用户名</param>
116        /// <param name="password">密码</param>
117        /// <returns>管理员实体类,不存在时返回null</returns>
118        public AdminInfo GetByNameAndPassword(string name, string password)
119        {
120            Gateway.SetDefaultDatabase("NBearConnectionString");
121            TAdmin tAdmin = Gateway.Default.Find<TAdmin>(TAdmin._.Name == name && TAdmin._.Password == password);
122            return tAdmin == null ? null : AdminConvertor.NBearEntityToCommonEntity(tAdmin);
123        }
124
125        /// <summary>
126        /// 按管理员名取得管理员信息
127        /// </summary>
128        /// <param name="name">管理员名</param>
129        /// <returns>管理员实体类</returns>
130        public AdminInfo GetByName(string name)
131        {
132            Gateway.SetDefaultDatabase("NBearConnectionString");
133            TAdmin tAdmin = Gateway.Default.Find<TAdmin>(TAdmin._.Name == name);
134            return tAdmin == null ? null : AdminConvertor.NBearEntityToCommonEntity(tAdmin);
135        }
136
137        /// <summary>
138        /// 取得全部管理员信息
139        /// </summary>
140        /// <returns>管理员实体类集合</returns>
141        public IList<AdminInfo> GetAll()
142        {
143            IList<AdminInfo> adminCollection = new List<AdminInfo>();
144            Gateway.SetDefaultDatabase("NBearConnectionString");
145            TAdmin[] tAdminCollection = Gateway.Default.FindArray<TAdmin>(null, TAdmin._.ID.Desc);
146            foreach (TAdmin tAdmin in tAdminCollection)
147            {
148                adminCollection.Add(AdminConvertor.NBearEntityToCommonEntity(tAdmin));
149            }
150            return adminCollection;
151        }
152    }
153}

基于.NET平台的分层架构实战(十)——业务逻辑层的实现

在这一篇文章中,将实现一个NGuestBook的业务逻辑层。

在实际应用中,业务逻辑层是至关重要的,他承载着整个系统最核心的部分,也是客户最关注的部分。这一部分的实现,通常需要技术专家和领域专家通力合作。当然,在本文章系列的Demo中,由于业务逻辑的简单性,这里看的可能还不是很明显。

在本篇文章的业务逻辑层实现中,业务逻辑层主要承担了以下职责:

1.对不同数据访问层的封装。使得表示层可以不关心具体的数据访问层。

2.业务逻辑数据的填充与转换。如管理员口令的加密。

3.核心业务的实现。这里很多业务逻辑只有一行代码,即一个业务逻辑方法恰好对应一个数据访问方法,但是也有通过多个数据访问方法实现业务的。如AdminBLL中的ChangePassword方法就调用了AdminDAL的GetByID和Update两个方法。另外,虽然许多方法只调用一个数据访问方法,但是从命名看也能看出两者着眼点的不同。如AdminDAL中的GetByNameAndPassword,这个名字显然是从数据库的角度看问题——指按照指定的Name和Password两个字段的值取出相应信息,至于这样做的业务意义它不需要知道。而AdminBLL中,调用它的方法叫Login,这是从业务角度看问题——即这个方法是管理员登录。

下面分步骤实现业务逻辑层:

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

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

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

Encryptor.cs:

 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4
 5namespace NGuestBook.Utility
 6{
 7    /// <summary>
 8    /// 辅助类-用于对敏感数据进行Hash散列,达到加密效果
 9    /// </summary>
10    public sealed class Encryptor
11    {
12        /// <summary>
13        /// 使用MD5算法求Hash散列
14        /// </summary>
15        /// <param name="text">明文</param>
16        /// <returns>散列值</returns>
17        public static string MD5Encrypt(string text)
18        {
19            return System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(text, "MD5");
20        }
21
22        /// <summary>
23        /// 使用SHA1算法求Hash散列
24        /// </summary>
25        /// <param name="text">明文</param>
26        /// <returns>散列值</returns>
27        public static string SHA1Encrypt(string text)
28        {
29            return System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(text, "SHA1");
30        }
31    }
32}

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

AdminBLL.cs:

 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4using NGuestBook.Entity;
 5using NGuestBook.Factory;
 6using NGuestBook.IBLL;
 7using NGuestBook.IDAL;
 8using NGuestBook.Utility;
 9
10namespace NGuestBook.IBLL
11{
12    /// <summary>
13    /// 业务逻辑层接口-管理员
14    /// </summary>
15    public class AdminBLL : IAdminBLL
16    {
17        /// <summary>
18        /// 添加管理员
19        /// </summary>
20        /// <param name="admin">新管理员实体类</param>
21        /// <returns>是否成功</returns>
22        public bool Add(AdminInfo admin)
23        {
24            admin.Password = Encryptor.MD5Encrypt(admin.Password);
25            return DALFactory.CreateAdminDAL().Insert(admin);
26        }
27
28        /// <summary>
29        /// 删除管理员
30        /// </summary>
31        /// <param name="id">欲删除的管理员的ID</param>
32        /// <returns>是否成功</returns>
33        public bool Remove(int id)
34        {
35            return DALFactory.CreateAdminDAL().Delete(id);
36        }
37
38        /// <summary>
39        /// 修改管理员密码
40        /// </summary>
41        /// <param name="id">欲修改密码的管理员的ID</param>
42        /// <param name="password">新密码</param>
43        /// <returns>是否成功</returns>
44        public bool ChangePassword(int id, string password)
45        {
46            password = Encryptor.MD5Encrypt(password);
47            AdminInfo admin = DALFactory.CreateAdminDAL().GetByID(id);
48            admin.Password = password;
49            return DALFactory.CreateAdminDAL().Update(admin);
50        }
51
52        /// <summary>
53        /// 管理员登录
54        /// </summary>
55        /// <param name="name">管理员登录名</param>
56        /// <param name="password">管理员密码</param>
57        /// <returns>如果登录成功,则返回相应管理员的实体类,否则返回null</returns>
58        public AdminInfo Login(string name, string password)
59        {
60            password = Encryptor.MD5Encrypt(password);
61            return DALFactory.CreateAdminDAL().GetByNameAndPassword(name, password);
62        }
63
64        /// <summary>
65        /// 取得全部管理员信息
66        /// </summary>
67        /// <returns>管理员实体类集合</returns>
68        public IList<AdminInfo> GetAll()
69        {
70            return DALFactory.CreateAdminDAL().GetAll();
71        }
72    }
73}

基于.NET平台的分层架构实战(十一)——表示层的实现

在这篇文章中,将讨论一下表示层的实现方法。

表示层是一个系统的“门脸”,不论你的系统设计的多么优秀,代码多么漂亮,系统的可扩展性多么高,但是最终用户接触到的大多是表示层的东西。所以,表示层的优劣对于用户最终对系统的评价至关重要。一般来说,表示层的优劣有一下两个评价指标:

1.美观。即外观设计漂亮,能给人美的感觉。

2.易用。即具有良好的用户体验,用户用起来舒服、顺手。

表示层的设计牵扯到很多非技术性问题,如美工、用户心理学等问题,但是在这篇文章中,将不过多涉及这些问题,一来是我的水平有限,二来是这些内容和本系列文章的关系不是很密切。这里将主要从技术实现的角度讨论表示层的设计。

一般来说,表示层的职责有以下两点:

1.接受用户的输入。

2.向用户呈现信息。

总体来说,就是与用户的交互。

而表示层的实现技术也是多种多样的,如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页面。设计好的页面如下图所示:(时间所迫,制作过于简陋,各位见谅)

图11.1、管理员登录界面

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

Login.aspx:

1<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="Login" %>
 2
 3<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 4
 5<html xmlns="http://www.w3.org/1999/xhtml" >
 6<head runat="server">
 7    <title>NGuestBook-管理员登录</title>
 8    <link href="Styles/Common.css" rel="stylesheet" type="text/css" />
 9    <link href="Styles/Login.aspx.css" rel="stylesheet" type="text/css" />
10</head>
11<body>
12    <form id="form1" runat="server">
13    <div id="container">
14        <div id="box">
15            <h1>NGuestBook管理员登录</h1>
16            <table id="forms" cellpadding="0" cellspacing="0">
17                <tr>
18                    <td>用户名:</td>
19                    <td><asp:TextBox ID="Name" TextMode="SingleLine" runat="server"></asp:TextBox></td>
20                </tr>
21                <tr>
22                    <td>密&nbsp;&nbsp;&nbsp;&nbsp;码:</td>
23                    <td><asp:TextBox ID="Password" TextMode="Password" runat="server"></asp:TextBox></td>
24                </tr>
25            </table>
26            <div id="buttons">
27                <asp:Button ID="Submit" runat="server" Text="登录" OnClick="Submit_Click" />
28                <asp:Button ID="Cancel" runat="server" Text="重填" OnClick="Cancel_Click" />
29            </div>
30        </div>
31    </div>
32    </form>
33</body>
34</html>
35

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

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

在我的工程中,有一个Styles文件夹,专门存放CSS文件。下面把Common.css和Login.aspx.css文件的代码附上:

Common.css

1*{}{
 2    margin:0;
 3    padding:0;
 4}
 5
 6h1,h2,h3,h4,h5,h6{}{
 7    font-size:12px;
 8}
 9
10#container{}{
11    width:100%;
12}

Login.aspx.css

1#box{}{
 2    width:40%;
 3    margin:200px auto auto auto;
 4    border:3px solid #036;
 5}
 6
 7h1{}{
 8    margin:1px;
 9    padding:5px 0;
10    font-size:14px;
11    color:#FFF;
12    background:#036;
13    text-align:center;
14}
15
16#forms{}{
17    margin:20px auto;
18    font-size:12px;
19    color:#036;
20}
21
22#forms input{}{
23    margin:10px;
24    border:0;
25    border-bottom:1px solid #036;
26    width:160px;
27}
28
29#buttons{}{
30    margin-bottom:20px;
31    text-align:center;
32}
33
34#buttons input{}{
35    margin:0 10px;
36}

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:

1using System;
 2using System.Data;
 3using System.Configuration;
 4using System.Collections;
 5using System.Web;
 6using System.Web.Security;
 7using System.Web.UI;
 8using System.Web.UI.WebControls;
 9using System.Web.UI.WebControls.WebParts;
10using System.Web.UI.HtmlControls;
11using NGuestBook.Entity;
12using NGuestBook.IBLL;
13using NGuestBook.Factory;
14
15public partial class Login : System.Web.UI.Page
16{
17    protected void Page_Load(object sender, EventArgs e)
18    {
19
20    }
21    protected void Cancel_Click(object sender, EventArgs e)
22    {
23        this.Name.Text = "";
24        this.Password.Text = "";
25    }
26    protected void Submit_Click(object sender, EventArgs e)
27    {
28        string administratorName = ConfigurationManager.AppSettings["AdministartorName"];
29        string administratorPassword = ConfigurationManager.AppSettings["AdministartorPassword"];
30
31        //如果是系统管理员,则以系统管理员身份登录到后台
32        if (this.Name.Text == administratorName && this.Password.Text == administratorPassword)
33        {
34            Session["Administrator"] = "Administrator";
35            Response.Redirect("~/Manage.aspx");
36            return;
37        }
38
39        //判断是否为普通管理员,如果是,则以管理员身份登录到留言本,否则显示登录失败
40        AdminInfo admin = BLLFactory.CreateAdminBLL().Login(this.Name.Text, this.Password.Text);
41        if (admin != null)
42        {
43            Session["Admin"] = admin;
44            Response.Redirect("~/Default.aspx");
45        }
46        else
47        {
48            Response.Redirect("~/Error.aspx?errMsg=登录失败");
49        }
50    }
51}

转 基于.NET平台的分层架构实战相关推荐

  1. 基于.NET平台的分层架构实战(二)——需求分析与数据库设计

    基于.NET平台的分层架构实战(五)--接口的设计与实现 · 基于.NET平台的分层架构实战(四)--实体类的设计与实现 · 基于.NET平台的分层架构实战(三)--架构概要设计 · 基于.NET平台 ...

  2. 基于.NET平台的分层架构实战(一)——综述

    通过浏览博客园的文章发现,很多朋友对分层架构特别感兴趣,刚好我刚做完的毕业设计就是专门研究.NET平台上分层架构的(题目叫"基于.NET平台的分层架构与设计模式应用研究").通过做 ...

  3. 一起谈.NET技术,发布NGuestBook(一个基于.NET平台的分层架构留言本小系统)

    发布NGuestBook的动机说明      大约在半年前,我在博客上发表了一个系列文章:<基于.NET平台的分层架构实战>.当时在讲解过程中用到了一个叫NGuestBook的案例,在那以 ...

  4. 发布NGuestBook(一个基于.NET平台的分层架构留言本小系统)

    发布NGuestBook的动机说明 大约在半年前,我在博客上发表了一个系列文章:<基于.NET平台的分层架构实战>.当时在讲解过程中用到了一个叫NGuestBook的案例,在那以后,有很多 ...

  5. 艾伟:基于.NET平台的Windows编程实战(四)—— 数据库操作类的编写

    本系列文章导航 基于.NET平台的Windows编程实战(一)--前言 基于.NET平台的Windows编程实战(二)-- 需求分析与数据库设计 基于.NET平台的Windows编程实战(四)-- 数 ...

  6. 微软账号登陆不上_企业信息化面临的问题,看看解决方案,基于微软平台的IT架构...

    哈喽,今日头条的小伙伴们大家好,我是你们的好朋友IT咨询顾问.小编曾经就工作过程中接触的企业IT环境存在的问题,陆陆续续搜集过一些案例,当时做的笔记是零散的,现在为了方便小伙伴们阅读,整理了一下笔记并 ...

  7. 基于云平台的物联网架构和原理

    基于云平台的物联网架构和原理 云的服务架构 云计算是通过各种技术手段服务客户的一种方式,包括三层服务模式,即最底层的IaaS(基础设施即服务),中间层的PaaS(平台即服务),和顶层的SaaS(软件即 ...

  8. DDD 实战 (5):战略设计之上下文映射和系统分层架构

    在上篇<DDD 实战 (4):战略设计之系统上下文和限界上下文>完成了限界上下文的识别(也就是系统"最粗粒度"的模块划分)后,我们需要对这些上下文之间的协作关系进行分析 ...

  9. DDD 实战 (5):限界上下文映射和系统分层架构

    在完成了限界上下文的识别(也就是系统"最粗粒度"的模块划分)后,我们需要对这些上下文之间的协作关系进行分析--即"限界上下文关系映射".也只有在完成上下文关系映 ...

  10. DDD领域驱动设计-分层架构实践

    代码结构 项目是使用maven构建的springboot项目 基于DDD领域驱动分层架构设计,分为接口层interfaces.应用层application.领域层domain.基础设施代理层infra ...

最新文章

  1. 批处理命令——goto 和 :
  2. MySQL原生密码认证
  3. web前端技术杂谈--css篇(1)--浅谈margin(续)
  4. windows上不同版本的CUDA、cudnn是否能够共用?
  5. 64位操作系统下IIS报“试图加载格式不正确的程序”错误
  6. ubuntu 配置url地址重定向协议
  7. 什么是 Silverlight?
  8. ValueError: Related model 'users.UserProfile' cannot be resolved
  9. 数据分析想要酷一点?这个超火的动态图表,你一定要学会
  10. html5实现饼图和线图-我们到底能走多远系列(34)
  11. .vue文件怎么使用_师父给了我一个 .proto 文件,我应该怎么使用?
  12. vertica--an hp company
  13. row_number()分页返回结果顺序不确定
  14. 等级保护三级测评-----云计算安全扩展要求
  15. 如何解决python 画图时 it could not find or load the Qt platform plugin “windows“ in “”的问题
  16. 企业oa系统是什么,有什么好用的办公软件推荐?
  17. 微信nickname乱码 php,CSV中微信名字乱码 问题
  18. 乒乓球比赛赛程_2020乒乓球比赛赛程表
  19. 概念数据模型(CDM)
  20. 外国人聊天常用的缩写(转)

热门文章

  1. 商业洞察力_正在进行的寻求洞察力和远见卓识
  2. 使用Pytorch的LSTM文本分类
  3. fastreport按条件查询_查询代价的
  4. oracle关联两个字段,oracle字符串连接函数,||使两个或多个字段拼成一个字段
  5. 计算器: 请输入两个数和一个符号,完成两个数的+ - * / % // **
  6. com+ system application 启动_[jvmsandboxrepeater 学习笔记][入门使用篇] 1 安装与启动
  7. 红旗Linux 网卡bond,Linux双网卡绑定一个IP的实现
  8. python import as 实例化_python中import list,dictionary常量在class实例化时遇到的坑
  9. 人工智能领域的新秀——Get智能写作
  10. 从零开始学 Web 之 jQuery(二)获取和操作元素的属性