论坛交流
首页办公自动化| 网页制作| 平面设计| 动画制作| 数据库开发| 程序设计| 全部视频教程
应用视频: Windows | Word2007 | Excel2007 | PowerPoint2007 | Dreamweaver 8 | Fireworks 8 | Flash 8 | Photoshop cs | CorelDraw 12
编程视频: C语言视频教程 | HTML | Div+Css布局 | Javascript | Access数据库 | Asp | Sql Server数据库Asp.net  | Flash AS
当前位置 > 文字教程 > Asp.net教程
Tag:静态页面,treeview,gridview,repeater,dataset,sqldatareader,ado.net,上传,三层,ajax,xml,留言本,新闻发布,商城,注入,存储过程,分页,安全,优化,xmlhttp,fso,jmail,application,session,防盗链,stream,无组件,组件,md5,乱码,缓存,加密,验证码,算法,cookies,ubb,正则表达式,水印,索引,日志,压缩,base64,url重写,控件,Web.config,JDBC,函数,内存,PDF,迁移,结构,破解,编译,配置,进程,分词,IIS,触发器,socket,form认证,登录,视频教程

Asp.net"三层结构"原理与用意学习入门教程(三)

文章类别:Asp.net | 发表日期:2008-10-31 13:21:06

对“三层结构”的深入理解——怎样才算是一个符合“三层结构”的Web应用程序?

在一个ASP.NET Web应用程序解决方案中,并不是说有aspx文件、有dll文件、还有数据库,就是“三层结构”的Web应用程序,这样的说法是不对的。也并不是说没有对数据库进行操作,就不是“三层结构”的。其实“三层结构”是功能实现上的三层。例如,在微软的ASP.NET示范实例“Duwamish7中,“表现层”被放置在“Web”项目中,“中间业务层”是放置在“BusinessFacade”项目中,“数据访问层”则是放置在“DataAccess”项目中……而在微软的另一个ASP.NET示范实例“PetShop3.0中,“表现层”被放置在“Web”项目中,“中间业务层”是放置在“BLL”项目中,而“数据访问层”则是放置在“SQLServerDAL”和“OracleDAL”两个项目中。Bincess.CN彬月论坛中,“表现层”是被放置在“WebForum”项目中,“中间业务(服务)层”是被放置在“InterService”项目中,而“数据访问层”是被放置在“SqlServerTask”项目中。

  如果只以分层的设计角度看,Duwamish7要比PetShop3.0复杂一些!而如果较为全面的比较二者,PetShop3.0则显得比较复杂。但我们先不讨论这些,对PetShop3.0Duwamish7的研究,并不是本文的重点。现在的问题就是:既然“三层结构”已经被分派到各自的项目中,那么剩下来的项目是做什么的呢?例如PetShop3.0中的“Model”、“IDAL”、“DALFactory”这三个项目,再例如Duwamish7中的“Common”项目,还有就是在Bincess.CN彬月论坛中的“Classes”、“DbTask”、这两个项目。它们究竟是做什么用的呢?

 

对“三层结构”的深入理解——从一家小餐馆说起

  一个“三层结构”的Web应用程序,就好象是一家小餐馆。

n            表 现 层,所有的.aspx页面就好像是这家餐馆的菜谱。

n            中间业务层,就像是餐馆的服务生。

n            数据访问层,就像是餐馆的大厨师傅。

n            而我们这些网站浏览者,就是去餐馆吃饭的吃客了……

 

 

我们去一家餐馆吃饭,首先得看他们的菜谱,然后唤来服务生,告诉他我们想要吃的菜肴。服务生记下来以后,便会马上去通知大厨师傅要烹制这些菜。大厨师傅收到通知后,马上起火烧菜。过了不久,服务生便把一道一道香喷喷的、热气腾腾的美味端到我们的桌位上——

而我们访问一个基于asp.net技术的网站的时候,首先打开的是一个aspx页面。这个aspx页面的后台程序会去调用中间业务层的相应函数来获取结果。中间业务层又会去调用数据访问层的相应函数来获取结果。在一个用户访问TraceLWord3打开ListLWord.aspx页面查看留言的时候,其后台程序ListLWord.aspx.cs会去调用位于中间业务层LWordServiceListLWord(DataSet ds)函数。然后这个函数又会去调用位于数据访问层AccessTaskListLWord(DataSet ds)函数。最后把结果显示出来……


对比一下示意图:

 

 

从示意图看,这两个过程是否非常相似呢?

 

不同的地方只是在于,去餐馆吃饭,需要吃客自己唤来服务生。而访问一个asp.net网站,菜单可以代替吃客唤来服务生。在最后的返回结果上,把结果返回给aspx页面,也就是等于把结果返回给浏览者了。

 

高度的“面向对象思想”的体现——封装

在我们去餐馆吃饭的这个过程中,像我这样在餐馆中的吃客,最关心的是什么呢?当然是:餐馆的饭菜是不是好吃,是不是很卫生?价格是不是公道?……而餐馆中的服务生会关心什么呢?应该是:要随时注意响应每位顾客的吩咐,要记住顾客在哪个桌位上?还要把顾客点的菜记在本子上……餐馆的大厨师傅会关心什么呢?应该是:一道菜肴的做法是什么?怎么提高烧菜的效率?研究新菜式……大厨师傅,烧好菜肴之后,只管把菜交给服务生就完事了。至于服务生把菜送到哪个桌位上去了?是哪个顾客吃了他做的菜,大厨师傅才不管咧——服务生只要记得把我点的菜肴端来,就成了。至于这菜是怎么烹饪的?顾客干麻要点这道菜?他才不管咧——而我,只要知道这菜味道不错,价格公道,干净卫生,其他的我才不管咧——

这里面不正是高度的体现了“面向对象思想”的“封装”原则吗?

无论大厨师傅在什么时候研究出新的菜式,都不会耽误我现在吃饭。就算服务生忘记我的桌位号是多少了,也不可能因此让大厨师傅忘记菜肴的做法?在我去餐馆吃饭的这个过程中,我、餐馆服务生、大厨师傅,是封装程度极高的三个个体。当其中的一个个体内部发生变化的时候,并不会波及到其他个体。这便是面向对象封装特性的一个益处!

 

土豆炖牛肉盖饭与实体规范

  在我工作过的第一家公司楼下,有一家成都风味的小餐馆,每天中午我都和几个同事一起去那家小餐馆吃饭。公司附近只有这么一家餐馆,不过那里的饭菜还算不错。我最喜欢那里的“土豆炖牛肉盖饭”,也很喜欢那里的“鸡蛋汤”,那种美味至今难忘……所谓“盖饭”,又称是“盖浇饭”,就是把烹饪好的菜肴直接遮盖在铺在盘子里的米饭上。例如“土豆炖牛肉盖饭”,就是把一锅热气腾腾的“土豆炖牛肉”遮盖在米饭上——


当我和同事再次来到这家餐馆吃饭,让我们想象以下这样的情形:

 

情形一:

我对服务生道:给我一份好吃的!

服务生道:什么好吃的?

我答道:一份好吃的——

三番几次……

我对服务生大怒道:好吃的,好吃的,你难道不明白吗?!——

 

这样的情况是没有可能发生的!因为我没有明确地说出来我到底要吃什么?所以服务生也没办法为我服务……

问题后果:我可能被送往附近医院的精神科……

 

情形二:

我对服务生道:给我一份土豆炖牛肉盖饭!

服务生对大厨师傅道:做一份宫爆鸡丁——

 

这样的情况是没有可能发生的!因为我非常明确地说出来我要吃土豆炖牛肉盖饭!但是服务生却给我端上了一盘宫爆鸡丁?!

问题后果:我会投诉这个服务生的……

 

情形三:

我对服务生道:给我一份土豆炖牛肉盖饭!

服务生对大厨师傅道:做一份土豆炖牛肉盖饭——

大厨师傅道:宫爆鸡丁做好了……

 

这样的情况是没有可能发生的!因为我非常明确地说出来我要吃土豆炖牛肉盖饭!服务生也很明确地要求大厨师傅做一份土豆炖牛肉盖饭。但是厨师却烹制了一盘宫爆鸡丁?!

问题后果:我会投诉这家餐馆的……

 

情形四:

我对服务生道:给一份土豆炖牛肉盖饭!

服务生对大厨师傅道:做一份土豆炖牛肉盖饭——

大厨师傅道:土豆炖牛肉盖饭做好了……

服务生把盖饭端上来,放到我所在的桌位。我看着香喷喷的土豆炖牛肉盖饭,举勺下口正要吃的时候,却突然发现这盘土豆炖牛肉盖饭变成了石头?!

 

这样的情况更是没有可能发生的!必定,现实生活不是《西游记》。必定,这篇文章是学术文章而不是《哈里波特》……

问题后果:……

 

如果上面这些荒唐的事情都成了现实,那么我肯定永远都不敢再来这家餐馆吃饭了。这些让我感到极大的不安。而在TraceLWord3这个项目中呢?似乎上面这些荒唐的事情都成真了。(我想,不仅仅是在TraceLWord3这样的项目中,作为这篇文章的读者,你是否也经历过像这一样荒唐的项目而全然未知呢?)

 

首先在ListLWord.aspx.cs文件


 

...

#048        private void LWord_DataBind()

#049        {

#050            DataSet ds=new DataSet();

#051            (new LWordService()).ListLWord(ds, @"LWordTable");

#052

#053            m_lwordListCtrl.DataSource=ds.Tables[@"LWordTable"].DefaultView;

#054            m_lwordListCtrl.DataBind();

#055        }

...

 

  在ListLWord.aspx.cs文件中,使用的是DataSet对象来取得留言板信息的。但是DataSet是不明确的!为什么这么说呢?行#051LWordService填充的DataSet中可以集合任意的数据表DataTable,而在这些被收集的DataTable中,不一定会有一个是我们期望得到的。假设,LWordService类中的ListLWord函数其函数内容是:

 

...

#006 namespace TraceLWord3.InterService

#007 {

...

#011    public class LWordService

#012    {

...

#019        public int ListLWord(DataSet ds, string tableName)

#020        {

#021            ds.Tables.Clear();

#022            ds.Tables.Add(new DataTable(tableName));

#023

#024            return 1;

#025        }

...

 

函数中清除了数据集中所有的表之后,加入了一个新的数据表后就匆匆返回了。这样作的后果,会直接影响ListLWord.aspx

 

...

#018 <asp:DataList ID="m_lwordListCtrl" Runat="Server">

#019 <ItemTemplate>

#020    <div>   <!--// 会提示找不到下面这两个字段 //-->

#021        <%# DataBinder.Eval(Container.DataItem, "PostTime") %>

#022        <%# DataBinder.Eval(Container.DataItem, "TextContent") %>

#023    </div>

#024 </ItemTemplate>

#025 </asp:DataList>

...

 

这和前面提到的“情形一”,一模一样!我没有明确地提出自己想要的饭菜,但是餐馆服务生却揣摩我的意思,擅自作主。

其次,再看LWordService.cs文件

 

...

#019        public int ListLWord(DataSet ds, string tableName)

#020        {

#021            return (new LWordTask()).ListLWord(ds, tableName);

#022        }

...

 

  在LWordService.cs文件中,也是使用DataSet对象来取得留言板信息的。这个DataSet同样的不明确,含糊不清的指令还在执行……行#021LWordTask填充的DataSet不一定会含有我们希望得到的表。即便是行#019中的DataSet参数已经明确的定义了每个表的结构,那么在带入行#021之后,可能也会变得混淆。例如,LWordTask类中的ListLWord函数其函数内容是:

 

...

#006 namespace TraceLWord2

#007 {

...

#011    public class LWordTask

#012    {

...

#022        public int ListLWord(DataSet ds, string tableName)

#023        {

#024            ds.Tables.Clear();

#025

#026            // SQL语句里选取了 [RegUser] 表而非 [LWord]

#027            string cmdText="SELECT * FROM [RegUser] ORDER BY [RegUserID] DESC";

#028

#029            OleDbConnection dbConn=new OleDbConnection(DB_CONN);

#030            OleDbDataAdapter dbAdp=new OleDbDataAdapter(cmdText, dbConn);

#031

#032            int count=dbAdp.Fill(ds, tableName);

#033

#034            return count;

#035        }

...

 

函数中清除了数据集中所有的表之后,选取了注册用户数据表[RegUser]DataSet进行填充并返回。也就是说,即便是LWordService.cs文件中#019中的DataSet参数已经明确的定义了每个表的结构,也可能会出现和前面提到的和“情形三”一样结果。

 

最后,再看看LWordTask.cs文件


 

...

#022        public int ListLWord(DataSet ds, string tableName)

#023        {

#024            string cmdText="SELECT * FROM [LWord] ORDER BY [LWordID] DESC";

#025

#026            OleDbConnection dbConn=new OleDbConnection(DB_CONN);

#027            OleDbDataAdapter dbAdp=new OleDbDataAdapter(cmdText, dbConn);

#028

#029            int count=dbAdp.Fill(ds, tableName);

#030

#031            return count;

#032        }

...

 

看到这里,我感到很是欣慰!我只能说我们的大厨师傅是一个厚道的人,而且还是很知我心的人。

 

  我们不能只坐在那里期盼着我们的程序会往好的方向发展,这样很被动。写出上面的这些程序段,必须小心翼翼。就连数据库表中的字段命名都要一审再审。一旦变化,就直接影响到位于“表现层”的ListLWord.aspx文件。仅仅是为了顺利的完成TraceLWord3这个“大型项目”,页面设计师要和程序员还有数据库管理员要进行额外的沟通。我们需要一个“土豆炖牛肉盖饭”式的强制标准!——

 

引入实体规范

 

为了达到一种“土豆炖牛肉盖饭”式的强制标准,所以在TraceLWord4中,引入了Classes项目。在这个项目里,只有一个LWord.cs程序文件。这是一个非常重要的文件,它属于“实体规范层”,如果是在一个Java项目中,Classes可以看作是:“实体Bean”。更完整的代码,可以在CodePackage/TraceLWord4目录中找到——

 

 

 

LWord.cs文件内容如下:

 

#001 using System;

#002

#003 namespace TraceLWord4.Classes

#004 {

#005    /// <summary>

#006    /// LWord 留言板类定义

#007    /// </summary>

#008    public class LWord

#009    {

#010        // 编号

#011        private int m_uniqueID;

#012        // 文本内容

#013        private string m_textContent;

#014        // 发送时间

#015        private DateTime m_postTime;

#016

#017        #region LWord 构造器

#018        /// <summary>

#019        /// LWord 默认构造器

#020        /// </summary>

#021        public LWord()

#022        {

#023        }

#024


#025        /// <summary>

#026        /// LWord 参数构造器

#027        /// </summary>

#028        /// <param name="uniqueID">留言编号</param>

#029        public LWord(int uniqueID)

#030        {

#031            this.UniqueID=uniqueID;

#032        }

#033        #endregion

#034

#035        /// <summary>

#036        /// 设置或获取留言编号

#037        /// </summary>

#038        public int UniqueID

#039        {

#040            set

#041            {

#042                this.m_uniqueID=(value<=0 ? 0 : value);

#043            }

#044

#045            get

#046            {

#047                return this.m_uniqueID;

#048            }

#049        }

#050

#051        /// <summary>

#052        /// 设置或获取留言内容

#053        /// </summary>

#054        public string TextContent

#055        {

#056            set

#057            {

#058                this.m_textContent=value;

#059            }

#060

#061            get

#062            {

#063                return this.m_textContent;

#064            }

#065        }

#066


#067        /// <summary>

#068        /// 设置或获取发送时间

#069        /// </summary>

#070        public DateTime PostTime

#071        {

#072            set

#073            {

#074                this.m_postTime=value;

#075            }

#076

#077            get

#078            {

#079                return this.m_postTime;

#080            }

#081        }

#082    }

#083 }

 

这个强制标准,LWordServiceLWordTask都必须遵守!所以LWordService相应的要做出变化:

 

#001 using System;

#002 using System.Data;

#003

#004 using TraceLWord4.AccessTask;  // 引用数据访问层

#005 using TraceLWord4.Classes;     // 引用实体规范层

#006

#007 namespace TraceLWord4.InterService

#008 {

#009    /// <summary>

#010    /// LWordService 留言板服务类

#011    /// </summary>

#012    public class LWordService

#013    {

#014        /// <summary>

#015        /// 读取 LWord 数据表,返回留言对象数组

#016        /// </summary>

#017        /// <returns></returns>

#018        public LWord[] ListLWord()

#019        {

#020            return (new LWordTask()).ListLWord();

#021        }

#022


#023        /// <summary>

#024        /// 发送留言信息到数据库

#025        /// </summary>

#026        /// <param name="newLWord">留言对象</param>

#027        public void PostLWord(LWord newLWord)

#028        {

#029            (new LWordTask()).PostLWord(newLWord);

#030        }

#031    }

#032 }

 

从行#018中可以看出,无论如何,ListLWord函数都要返回一个LWord数组!这个数组可能为空值,但是一旦数组的长度不为零,那么其中的元素必定是一个LWord类对象!而一个LWord类对象,就一定有TextContentPostTime这两个属性!这个要比DataSet类对象作为参数的形式明确得多……同样的,LWordTask也要做出反应:

 

#001 using System;

#002 using System.Collections;

#003 using System.Data;

#004 using System.Data.OleDb;

#005 using System.Web;

#006

#007 using TraceLWord4.Classes;     // 引用实体规范层

#008

#009 namespace TraceLWord4.AccessTask

#010 {

#011    /// <summary>

#012    /// LWordTask 留言板任务类

#013    /// </summary>

#014    public class LWordTask

#015    {

#016        // 数据库连接字符串

#017        private const string DB_CONN=@"PROVIDER=Microsoft.Jet.OLEDB.4.0;

DATA Source=C:\DbFs\TraceLWordDb.mdb";

#018

#019        /// <summary>

#020        /// 读取 LWord 数据表,返回留言对象数组

#021        /// </summary>

#022        /// <returns></returns>

#023        public LWord[] ListLWord()

#024        {

#025            // 留言对象集合

#026            ArrayList lwordList=new ArrayList();

#027

#028            string cmdText="SELECT * FROM [LWord] ORDER BY [LWordID] DESC";

#029


#030            OleDbConnection dbConn=new OleDbConnection(DB_CONN);

#031            OleDbCommand dbCmd=new OleDbCommand(cmdText, dbConn);

#032

#033            try

#034            {

#035                dbConn.Open();

#036                OleDbDataReader dr=dbCmd.ExecuteReader();

#037

#038                while(dr.Read())

#039                {

#040                    LWord lword=new LWord();

#041

#042                    // 设置留言编号

#043                    lword.UniqueID=(int)dr["LWordID"];

#044                    // 留言内容

#045                    lword.TextContent=(string)dr["TextContent"];

#046                    // 发送时间

#047                    lword.PostTime=(DateTime)dr["PostTime"];

#048

#049                    // 加入留言对象到集合

#050                    lwordList.Add(lword);

#051                }

#052            }

#053            catch

#054            {

#055                throw;

#056            }

#057            finally

#058            {

#059                dbConn.Close();

#060            }

#061

#062            // 将集合转型为数组并返回给调用者

#063            return (LWord[])lwordList.ToArray(typeof(TraceLWord4.Classes.LWord));

#064        }

#065

#066        /// <summary>

#067        /// 发送留言信息到数据库

#068        /// </summary>

#069        /// <param name="newLWord">留言对象</param>

#070        public void PostLWord(LWord newLWord)

#071        {

#072            // 留言内容不能为空

#073            if(newLWord==null || newLWord.TextContent==null || newLWord.TextContent=="")

#074                throw new Exception("留言内容为空");

#075

#076            string cmdText="INSERT INTO [LWord]([TextContent]) VALUES(@TextContent)";

#077

#078            OleDbConnection dbConn=new OleDbConnection(DB_CONN);

#079            OleDbCommand dbCmd=new OleDbCommand(cmdText, dbConn);

#080

#081            // 设置留言内容

#082            dbCmd.Parameters.Add(new OleDbParameter("@TextContent",

OleDbType.LongVarWChar));

#083            dbCmd.Parameters["@TextContent"].Value=newLWord.TextContent;

#084

#085            try

#086            {

#087                dbConn.Open();

#088                dbCmd.ExecuteNonQuery();

#089            }

#090            catch

#091            {

#092                throw;

#093            }

#094            finally

#095            {

#096                dbConn.Close();

#097            }

#098        }

#099    }

#100 }

 

  这样,即便是将LWordTask.cs文件中的ListLWords方法修改成访问[RegUser]数据表的代码,也依然不会影响到外观层。因为函数只返回一个LWord类型的数组。再有,因为位于外观层的重复器控件绑定的是LWord类对象,而LWord类中就必有对TextContent字段的定义。这样也就达到了规范数据访问层返回结果的目的。这便是为什么在Duwamish7中会出现Common项目的原因。不知道你现在看明白了么?而Bincess.CN的做法和PetShop3.0一样,是通过自定义类来达到实体规范层的目的!PetShop3.0是通过Modal项目,而Bincess.CN则是通过Classes项目。

视频教程列表
文章教程搜索
 
Asp.net推荐教程
Asp.net热门教程
看全部视频教程
购买方式/价格
购买视频教程: 咨询客服
tel:15972130058