1、第9章 应用程序架构9.1 应用程序架构应用程序架构9.2 校园在线超市系统多层架构实现校园在线超市系统多层架构实现【本章提要本章提要】本章介绍了企业级应用多层设计的模式,阐述了应用分层的好处。并以校园在线超市用户注册模块为例,详细介绍了系统分层架构的实现过程。【学习目标学习目标】理解从两层架构发展成多层架构。掌握将多层体系架构的理念融入到Web应用项目的设计和实施方法。9.1.1 将应用分层的好处将应用分层的好处在大型公司里往往运行着大规模的软件系统,开发人员和分析师们专注于不同的应用层级。这确实是必须的,因为不可能让开发人员都理解某个层级所有的运行细节,若那样,则系统实在是太庞大了。9.1
2、 应用程序架构应用程序架构想象一下某个保险公司里的应用程序必须完成的所有功能。从最简化的流程来说,保险公司要能接收新的保险单,评估与保险单相关的风险,向投保者开出账单,打印保险单然后将它邮寄出去,处理保险单的索赔,更新保险单,如果过期(或者没有支付费用)则取消保险单,等等。这一巨大的流程显然不仅仅是一个桌面应用程序所能处理的。无论用户是不是想这么做,Web应用还是分层的好。如果用户是用SQL Server数据库存储数据的,那么数据库本身就已经是一个从Web服务器上分离出来的层了(即使物理上它们是在同一台计算机里)。作为一个面向对象的平台,ASP.NET使得这种分层操作更加容易了。应用程序分层使
3、得整个开发更加容易,因为分层将某些特定目的操作封装成一个个独立的模块,它们可以单独维护和修改,同时也分散了维护的工作量。例如,一个简单的在线零售程序采用了分层的思想,如图9-1所示。从图中可以看出物理硬件分层与应用程序分层的结合。实际上有三个分层运行于一个Web服务器上,第一层是Web站点的用户界面层(User Interface),它由业务规则层支持,业务规则层(Business Rules Layer)决定什么样的数据将被提供给用户界面层,业务规则层与数据访问层交互,而数据访问层则通过一个事务通道程序来获取所需要的数据,其他的客户服务和仓库系统也通过事务通道程序来获得所需的数据。图9-1
4、在线零售应用程序的分层每一层都仅仅关注于自身的功能实现,如业务规则层不需要知道任何关于数据存储方面的信息,它只需要了解由数据访问层提供的接口即可。事实上,业务规则层完全不必关心使用什么样的数据库,或者是否存在一个数据库。它只需要了解有一个数据访问层能用于交互,以及它可以通过一个公共的接口来交互数据即可。尽管整个系统的完善需要集成测试,但是如果各层之间一致认同的接口没有发生改变,则我们可以在不干扰其他层的情况下改变任何一层的内部结构。9.1.2 n级架构级架构任何关于应用程序架构的讨论中,如果没有提到经典的n级架构,那么就是不完整的。“n”代表任何数字,适用于描述我们所创建的应用程序。层(lay
5、er)或级(tier)的数目,由应用程序块的需求和结构来决定。一般而言,甚至最基本的Web应用程序都能够被分离成三层:用户界面层(UI)、业务规则层(Business Rules Layer)以及数据层(Data Layer)。用户界面层就是用户能够见到的HTML和Web控件,业务规则层则承担着应用程序最重要的角色,而数据层则是数据库或者其他数据访问代码,这取决于应用程序的结构。如果需要,可以将应用程序分得更细:用户可以拥有用户界面(.aspx页面)、作为用户界面“粘合剂”而被页面继承的类或局部类、业务规则层(业务规则类)、数据访问层(数据访问类)以及数据层本身(数据库)。假如由于某种原因,决
6、定将SQL Server数据库替换为Oracle数据库,则如果没有将应用程序分层,而且把数据访问代码放在.aspx页面里,那么用户将不得不冒着把事情搞砸的风险,检查所有页面。这显然不是一种有效的代码管理方式。实际上,每个页面间操作数据的方式都不同。如果把所有的数据访问代码都放在单个层里,如一个拥有操作数据方法的类,这样就可以在不破坏业务规则或者用户界面的情况下对代码进行修改。另一种很常见的情况是,假设在电子商务程序中,业务规则要求改变税款的计算方式,数据库没有变化,站点本身也不需要变化,仅仅是逻辑改变了,那就意味着只需要改变业务规则层中的类即可。再者,如果逻辑将创建在页面中或者是由某些数据库存
7、储过程的计算所组成的,那么这项修改操作将变得十分困难。在一个极端的例子里,税率可能经常改变,那么就不能将税率硬编码到代码中。一般的解决方式是将税率存储在数据库中,并创建用户易于操作的相关工具。9.2.1 系统架构设计系统架构设计微软.NET平台可以方便快速地开发和部署多层架构应用程序。在校园在线超市系统中,其架构在逻辑上划分成数据实体层(DML)、数据访问层(DAL)、业务逻辑层(BLL)和应用层(UI)四层,如图9-2所示。9.2 校园在线超市系统多层架构实现校园在线超市系统多层架构实现图9-2 系统分层架构设计从图中看出,这种分层结构易于理解,同时也使应用程序的维护和修改变得更加容易。假定
8、我们要为Microsoft Access数据库创建一个新的数据访问层,则只需要改变数据访问层代码即可。通过用户界面进行的输入和输出数据的过程,实际上是在创建业务逻辑层的各种对象。例如,在应用程序中添加一个新的商品时,只需要创建一个商品对象,而不需要了解任何关于数据库中的具体细节。下面以“用户注册模块”为例,说明校园在线超市系统的多层实现过程。为了区分校园在线超市两层实现的定义,这里将解决方案命名为“NetShop”,在数据库设计和命名上也做一些改进。9.2.2 数据实体层实现数据实体层实现数据实体层(DML,Data Model Layer)主要功能是实现关系数据库实体和实体关系表到应用程序对
9、象的映射。数据库中有名为“Users”的实体表,用来存储注册用户的相关信息。打开VS 2005,创建一个新网站,新建名为“Model”的文件夹,命名空间为“NetShop.Model”,在该文件夹下添加类库文件User.cs,创建用户类。相应代码编写如下:using System;namespace NetShop.Model/实体类Users(属性说明自动提取数据库字段的描述信息)/Serializablepublic class Userspublic Users()#region Modelprivate int _s_id;/用户IDprivate string _s_name;/用户
10、名称private string _s_password;/用户密码private string _s_qq;/用户QQprivate string _s_email;/Emailprivate string _s_phone;/联系电话private string _s_address;/通信地址private string _s_sex;/性别private string _s_idcard;/身份证号private DateTime?_s_date;/出生日期private string _s_answer;/密码答案private string _s_question;/密码问题pri
11、vate int _s_type;/用户类型private string _s_photo;/用户图片private int?_s_adminid;/用户ID/public int s_idset _s_id=value;getreturn _s_id;/用户名称/public string s_nameset _s_name=value;getreturn _s_name;/用户密码/public string s_passwordset _s_password=value;getreturn _s_password;/用户QQ/public string s_qqset _s_qq=val
12、ue;getreturn _s_qq;/Email/public string s_emailset _s_email=value;getreturn _s_email;/联系电话/public string s_phoneset _s_phone=value;getreturn _s_phone;/通信地址/public string s_addressset _s_address=value;getreturn _s_address;/性别/public string s_sexset _s_sex=value;getreturn _s_sex;/身份证号/public string s_
13、idcardset _s_idcard=value;getreturn _s_idcard;/出生日期/public DateTime s_date /这里的?表示该属性可以为空set _s_date=value;getreturn _s_date;/密码答案/public string s_answerset _s_answer=value;getreturn _s_answer;/密码问题/public string s_questionset _s_question=value;getreturn _s_question;/用户类型/public int?s_typeset _s_typ
14、e=value;getreturn _s_type;/用户图片/public string s_photoset _s_photo=value;getreturn _s_photo;/public int?s_adminidset _s_adminid=value;getreturn _s_adminid;#endregion Model9.2.3 数据访问层实现数据访问层实现当用户注册时,会向Users表插入一条记录。在本系统中,数据访问层(IDAL,Data Access Layer)的实现分成以下三个步骤:定义对象接口类。实现数据访问接口类。定义数据工厂类创建对象接口。1定义对象接口类定
15、义对象接口类在解决方案中创建文件夹“IDAL”,并定义此文件夹下命名空间为“NetShop.IDAL”、名为IUsers的接口。这样做是为了让数据访问层易于替换并支持其他数据库,使用接口来强制自己实现整个数据访问类的结构。代码如下:using System;using System.Data;namespace NetShop.IDAL/接口层IUsers 的摘要说明/public interface IUsers#region 成员方法/得到最大ID/int GetMaxId();/是否存在该记录/bool Exists(int s_id);/增加一条数据/int Add(NetShop.M
16、odel.Users model);/更新一条数据/bool Update(NetShop.Model.Users model);/删除一条数据/bool Delete(int s_id);/得到一个对象实体/NetShop.Model.Users GetModel(int s_id);/获得数据列表/DataSet GetList(string strWhere);/获得前几行数据/DataSet GetList(int Top,string strWhere,string filedOrder);/获得数据列表 /int GetUser(string strWhere);/修改基本信息 /
17、bool Updatajiben(NetShop.Model.Users model);/修改问题答案 /bool UpdataQuestion(NetShop.Model.Users model);/修改密码 /bool Updatapassword(NetShop.Model.Users model);#endregion 成员方法从上述代码上看,接口没有任何实现代码,它的根本目的在于让我们知道实现它的类应该具有的结构。这种方式对问题的理解更加直观,但是在业务逻辑层上要求能够访问接口实现类中的所有方法。要实现这个目的,可以根据web.config文件中的配置,结合反射来加载数据访问类并将其
18、缓存。在数据工厂中,DataAccess类中CreateUsers的静态方法实现了这个功能。2实现数据访问接口类实现数据访问接口类在解决方案中,创建名为“SQLServerDAL”的文件夹,命名空间名为NetShop.SQLServerDAL,在此中建立名为“Users.cs”的类,实现IUsers接口。代码如下:using System;using System.Data;using System.Text;using System.Data.SqlClient;using NetShop.IDAL;using NetShop.DBUtility;/请先添加引用namespace NetSh
19、op.SQLServerDAL/数据访问类Users/public class Users:IUserspublic Users()#region 成员方法/增加一条数据/public int Add(NetShop.Model.Users model)StringBuilder strSql=new StringBuilder();strSql.Append(insert into NetShop_Users();strSql.Append(s_name,s_password,s_qq,s_email,s_phone,s_address,s_sex,s_idcard,s_date,s_ans
20、wer,s_question,s_type,s_photo,s_adminid);strSql.Append(values();strSql.Append(s_name,s_password,s_qq,s_email,s_phone,s_address,s_sex,s_idcard,s_date,s_answer,s_question,s_type,s_photo,s_adminid);strSql.Append(;select IDENTITY);SqlParameter parameters=new SqlParameter(s_name,SqlDbType.VarChar,16),new
21、 SqlParameter(s_password,SqlDbType.VarChar,24),new SqlParameter(s_qq,SqlDbType.VarChar,12),new SqlParameter(s_email,SqlDbType.VarChar,30),new SqlParameter(s_phone,SqlDbType.VarChar,15),new SqlParameter(s_address,SqlDbType.VarChar,100),new SqlParameter(s_sex,SqlDbType.VarChar,2),new SqlParameter(s_id
22、card,SqlDbType.VarChar,20),new SqlParameter(s_date,SqlDbType.DateTime),new SqlParameter(s_answer,SqlDbType.VarChar,100),new SqlParameter(s_question,SqlDbType.VarChar,100),new SqlParameter(s_type,SqlDbType.Int,4),new SqlParameter(s_photo,SqlDbType.VarChar,100),new SqlParameter(s_adminid,SqlDbType.Int
23、,4);parameters0.Value=model.s_name;parameters1.Value=model.s_password;parameters2.Value=model.s_qq;parameters3.Value=model.s_email;parameters4.Value=model.s_phone;parameters5.Value=model.s_address;parameters6.Value=model.s_sex;parameters7.Value=model.s_idcard;parameters8.Value=model.s_date;parameter
24、s9.Value=model.s_answer;parameters10.Value=model.s_question;parameters11.Value=model.s_type;parameters12.Value=model.s_photo;parameters13.Value=model.s_adminid;object obj=DbHelperSQL.GetSingle(strSql.ToString(),parameters);if(obj=null)return 1;elsereturn Convert.ToInt32(obj);3定义数据工厂类创建对象接口定义数据工厂类创建对
25、象接口在解决方案中,创建名为“DALFactory”的文件夹,命名空间名为NetShop.DALFactory,在此中建立名为“DataAccess.cs”的类来定义数据工厂,以实现数据访问。代码如下:using System;using System.Reflection;using System.Configuration;namespace NetShop.DALFactory/Abstract Factory pattern to create the DAL/public sealed class DataAccess private static readonly string A
26、ssemblyPath=ConfigurationManager.AppSettingsDAL;/创建Users数据层接口/public static NetShop.IDAL.IUsers CreateUsers()string ClassNamespace=AssemblyPath+.Users;object objType=CreateObject(AssemblyPath,ClassNamespace);return(NetShop.IDAL.IUsers)objType;9.2.4 业务逻辑层实现业务逻辑层实现为了实现对用户注册信息的插入,业务逻辑层(BLL,Business Log
27、ic Layer)需要调用来自数据访问层的方法,并将相关值作为参数传递给它。BLL命名空间下的USers类中的Add方法实现了这一过程。代码如下所示:using NetShop.IDAL;using NetShop.DBUtility;using System.Text;namespace NetShop.BLL/业务逻辑类Users 的摘要说明/public class Users/定义访问用户对象的接口变量IUsersprivate readonly IUsers dal=DataAccess.CreateUsers();public Users()#region 成员方法/增加一条数据/
28、public int Add(NetShop.Model.Users model)return dal.Add(model);这个方法完成了许多完全适合于业务逻辑层,并且不希望留给用户界面层或数据访问层的工作。9.2.5 用户接口层实现用户接口层实现通过对业务逻辑进行封装后,用户接口层(UI,User Interface)的设计就主要在页面设计和参数的传递了。用户接口层上可以在不知道应用程序其他部分细节的情况下调用业务逻辑层的方法来实现相应的业务逻辑,达到应用层和业务逻辑层的协同工作。其部分代码如下:public partial class UiControl_user_add:System.
29、Web.UI.UserControl NetShop.BLL.Users us=new NetShop.BLL.Users();NetShop.Model.Users user=new NetShop.Model.Users();string image=UpFiles/UserImages/none.gif;protected void BtnSubmit_Click(object sender,EventArgs e)if(upImage()user.s_name=tb_user_nam.Text.Trim();user.s_password=Util.getpwd(tb_password
30、.Text.Trim().ToString();user.s_qq=tb_password.Text.Trim();user.s_email=tb_email.Text.Trim();user.s_phone=tb_phto.Text.Trim();user.s_address=tb_adrees.Text.Trim();user.s_sex=tb_sex.SelectedValue.ToString();user.s_idcard=tb_user_id.Text.Trim();user.s_date=DateTime.Now;user.s_photo=image;if(us.Add(user
31、)0)JavaScript.alert(注册成功);else JavaScript.alert(注册失败);else JavaScript.alert(头像上传失败);private bool upImage()string filePath=Server.MapPath(/Web/UpFiles/UserImages/);if(fu_up.HasFile)int sts=fu_up.FileName.LastIndexOf(.);string str=fu_up.FileName.Substring(sts);if(str=.jpg|str=.jpeg|str=.gif|str=.bmp|s
32、tr=.png)if(File.Exists(filePath+fu_up.FileName)try File.Delete(filePath);catch JavaScript.alert(图片文件夹拒绝访问请修改);return false;string filname=Util.getFileName()+str;try fu_up.SaveAs(filePath+filname);catch JavaScript.alert(图片上传失败);return false;image=UpFiles/UserImages/+filname;return true;else JavaScript.alert(文件格式不对);return false;return true;分层架构将应用分散成更加易于管理、维护、替换和修正的小模块,同时它也允许开发团队专注于自己所擅长的领域,这一概念在Web应用中一般被划分成设计、编程和数据优化等几个方面。