用多层架构构建一个简易留言本
ASP.NET 终于可以让 Web 开发人员从 ASP 的面条代码中脱身出来,以全新的方式来构建
Web 站点,就像 Windows Application 一样,我们同样可以用面向对象的、多层的方式来组
织和构建 Web Application 。
下面给出的是一个功能非常简单的留言本程序,旨在揭示 ASP.NET 强大的能力和全新的
开发方式。如果只相对留言本程序本身而言,大家可能怀疑用这么多的气力实现如此简单的
程序是否值得,但我说过,例子只是用来说明问题和描述解决方案。其实我认为,从维护和
扩充的角度来说,即使再简单的程序,从一开始就进行良好的设计也是非常值得的。
留言本采用多层的方式来构建,下面的介绍为了方便大家理解,并未按照层次的顺序
介绍:
一、数据实体 (CMessageData 类 )
CMessageData 派生自 DataSet ,用来维护留言数据,在构造函数中,调用
CreateDataTables ()来增加一个用来保存留言数据的DataTable,并加到自身的
DataTable集合中。静态属性TableMapping用来描述这个DataTable的DataColumn和数
据库中物理字段的映射关系,数据访问层将使用这个属性来填充数据进CMessageData
对象中。
public class CMessageData : DataSet {
public CMessageData () {
this . CreateDataTables ();
}
public static DataTableMapping TableMapping {
get {
DataTableMapping result = new DataTableMapping ("t_gbook_postinfo", "MessageTable");
result . ColumnMappings . Add ("id", "Id");
result . ColumnMappings . Add ("last_reply_time", "LastReplyTime");
// … ..
return result ;
}
}
private void CreateDataTables () {
DataTable dt = new DataTable ("MessageTable");
dt . Columns . Add ("Id", typeof ( Int32 ));
dt . Columns . Add ("LastReplyTime", typeof ( DateTime ));
// … ..
dt . Columns ["Id"]. AutoIncrement = true ;
dt . Columns ["Id"]. AutoIncrementSeed = 0;
dt . Columns ["Id"]. AutoIncrementStep = -1;
dt . PrimaryKey = new DataColumn [] { dt . Columns ["Id"]};
this . Tables . Add ( dt );
}
AddedNewRow属性返回一个新增进数据表的、空的DataRow,用于给逻辑层填充。
FillDataFormDataBase()的两个重载调用数据访问层的相应方法来填充一个新的
CMessageData对象并返回。UpdateToDatabase用于讲自身的数据更改更新回数据库。
public DataRow AddedNewRow () …
public static CMessageData FillDataFromDatabase ( Int32 startRecord , Int32 maxRecord ) …
public static CMessageData FillDataFromDatabase ( Int32 id ) …
public void UpdateToDatabase () …
二、数据访问层 (CDataAccess 类 )
负责连接数据库,进行 SIUD(Select,Insert,Update,Delete) 操作。数据连接信息放在
AppParameters.xml 文件中。
FillMessageData()的两个重载创建新的CMessageData对象,填充数据,然后返
回:
public static CMessageData FillMessageData ( Int32 startRecord , Int32 maxRecord )
public static CMessageData FillMessageData ( Int32 id )
UpdateMessageData()把参数中的CMessageData对象所作出的更改更新回数据
库:
public static Int32 UpdateMessageData ( CMessageData messageData ) {
OleDbConnection conn = new OleDbConnection ( CAppParameters . OleDbConnectionString );
OleDbCommand cmdSelect = new OleDbCommand ("Select username,last_reply_time,guest_name,guest_email,guest_website_name,guest_website_url,guest_oicq,guest_ip,guest_post_time,guest_text,reply_data From t_gbook_postinfo", conn );
OleDbCommand cmdInsert = new OleDbCommand ();
cmdInsert . Connection = conn ;
cmdInsert . CommandText = "Insert Into t_gbook_postinfo (last_reply_time,guest_name,guest_email,guest_website_name,guest_website_url,guest_oicq,guest_ip,guest_post_time,guest_text,reply_data) Values (@last_reply_time,@guest_name,@guest_email,@guest_website_name,@guest_website_url,@guest_oicq,@guest_ip,@guest_post_time,@guest_text,@reply_data)";
cmdInsert . Parameters . Add ("@last_reply_time", OleDbType . DBDate , 0, "last_reply_time");
cmdInsert . Parameters . Add ("@guest_name", OleDbType . VarWChar , 255, "guest_name");
// …
OleDbCommand cmdUpdate = new OleDbCommand ();
cmdUpdate . Connection = conn ;
cmdUpdate . CommandText = "Update t_gbook_postinfo Set last_reply_time=@last_reply_time,guest_name=@guest_name,guest_email=@guest_email,guest_website_name=@guest_website_name,guest_website_url=@guest_website_url,guest_oicq=@guest_oicq,guest_ip=@guest_ip,guest_post_time=@guest_post_time,guest_text=@guest_text,reply_data=@reply_data Where (id=@Original_id)";
cmdUpdate . Parameters . Add ("@last_reply_time", OleDbType . DBDate , 0, "last_reply_time");
cmdUpdate . Parameters . Add ("@guest_name", OleDbType . VarWChar , 255, "guest_name");
// …
OleDbCommand cmdDelete = new OleDbCommand ();
cmdDelete . Connection = conn ;
cmdDelete . CommandText = @"Delete From t_gbook_postinfo where (id = @Original_id)";
cmdDelete . Parameters . Add ("@Original_id", OleDbType . Integer , 0, "id"). SourceVersion = DataRowVersion . Original ;
OleDbDataAdapter ada = new OleDbDataAdapter ( cmdSelect );
ada . InsertCommand = cmdInsert ;
ada . UpdateCommand = cmdUpdate ;
ada . DeleteCommand = cmdDelete ;
ada . TableMappings . Add ( CMessageData . TableMapping );
return ada . Update ( messageData , "t_gbook_postinfo");
}
把数据访问层单独提取出来的好处就是其他层都不会直接和数据库打交道,如果
我们把数据库从 Access改成SqlServer只需要用一个新的CDataAccess类替换现在
的即可。在源码中,就有一个使用了Odbc.Net实现的COdbcDataAccess,用这个
替换掉CDataAccess不会对程序中其他部分产生任何影响,我们可以利用
Odbc.Net的访问能力,把数据库改为Oracle、Forpro等。
三、逻辑层
这个留言本的逻辑层很简单,由三个类组成, CMessage用来描述一条留言,
CReply用来描述一条回复,CReplyCollection集合类用来描述多条回复。
CMessage提供了一个重载的构造函数:
public CMessage ( DataRow row )
我们可以用 CMessageData中个一个DataRow的数据来初始化一个CMessage对象。
public void FillDataRow ( DataRow row )
这个函数则把自身的数据填充进参数中的 DataRow对象。我们用类似:
GetMessage (). FillDataRow ( messageData . AddedNewRow ())
这样的代码就可以把一条新的留言内容新增到一个 CMessageData对象中,其中
GetMessage()是页面上收集用户填入的数据并返回一个CMessage的一个方法。
public CReplyCollection Replys
这个属性用来公开对自身这条留言的所有回复。
四、界面层 - 用户控件
为了方便我们把一个 CMessage对象和页面上显示出来的一条留言绑定在一起,把
一个CReply对象与页面上显示出来的一条回复绑定在一起,我们制作两个
UserControl。MessageBlock控件用来显示一条留言,它通过属性:
public CMessage Message
来对象公开 CMessage接口,我们只需要把一个CMessage对象赋值给这个属性,就
可以让这个控件显示CMessage对象所表示的留言的内容。
ReplyBlock控件用来显示一条回复,同样通过属性:
public CReply Reply
来公开一个 CReply类型的接口。
在 MessageBlock控件中,我们根据对应的CMessage对象的Replys属性中所包含的
回复,通过LoadControl()方法来动态载入ReplyBlock控件,并放置在一个
PlaceHolder类型的Web控件中。
五、界面层 - 页面
现在页面的显示非常简单了,我们在主页面 (default.aspx.cs)中创建一个
CMessageData对象,填充数据,再用LoadControl()方法来载入MessageBlock控
件来显示留言就可以了。
CMessageData messageData = CMessageData . FillDataFromDatabase (( iPage - 1) * iPageSize , iPageSize );
for ( Int32 i = 0; i < messageData . Count ; ++ i ) {
MessageBlock msg = ( MessageBlock ) LoadControl ("MessageBlock.ascx");
msg . Message = new CMessage ( messageData . Tables ["MessageTable"]. Rows [ i ]);
hldMessage . Controls . Add ( msg );
}
以一个功能齐全的留言本来衡量,我们上面构建的留言本缺少删贴、管理功能,
但是只要基础架构出来,完善和扩展功能是非常简单的。
上面的留言本展示了一个基础的程序架构,真正大型程序的架构可能要复杂上很
多。比如数据实体类我们可以把字段信息和映射信息放入一个 XML文件中,然后
我们只需要创建一个通用的数据实体类,通过载入不同的XML文件就可以描述不
同的数据实体;在数据复杂的情况下,维护各个数据