重构ASP.NET程序----继承
原程序,可以从下面地址下载:http://download.cnblogs.com/insus/ASPDOTNET/Ref_Org.rar
数据库SQL Server 2008 R2,数据名为[Demo],它有四张表[UnitCode1], [UnitCode2], [UnitCode3], [UnitCode4],每个表有几个字段[Unit1~4],[Description],[CreateBy],[CreateDate],[UpdateBy],[UpdateDate],此四个表的主键分别为[Unit1],[Unit2],[Unit3]和[Unit4],其余字段名称四张表都一样。数据库还有各个表的相关的存储过程。
程序中有一个接口,是为了设置网页标题。数据库四张表对应的类别,程序应用了母版,有5个网页,Default.aspx, UnitCode1.aspx, UnitCode2.aspx, UnitCode3.aspx和UnitCode4.aspx 。每个网页分别也是对各自的表进行添加,显示,更新以及删除记录的功能。
其实,这就是一个小程序,基本的功能都齐全。
-------------------------------------------------------------------------------------------------------------------------------------
根据此篇博文,我们来学习一下继承。
打开程序,我们会到四个网页的cs代码与四个类别98%相同,只是每个表的主键名,以及存储过程名称等不一样。继承是把相同而共用的属性,方法,函数放置于父类中,这样继承的类,就是使用到这些共用的protected或public的程序块。尽可能简化,变化的地方尽量未端化去维护。
我们创建一个父类,比如叫BaseUnitCode.cs吧。
首先我们对比四个类别中,属性部分,只有
private string _Unit1; public string Unit1 { get { return _Unit1; } set { _Unit1 = value; } }
不相同,因此我们就把它留在原本的类别中。其余的属性都移至父类BaseUnitCode,还有一句,就是逻辑处理的类实例
BusinessBase objBusinessBase = new BusinessBase();
在每个类别中也一样,因此也移至父类,下面是刚才重构好的父类。
接下来,我看到每个类别的GetAll(), Insert(), Update(), Delete()方法中,只有一些参数名,参数值,以及存储过程名有差异。下面是每个方法移至父类之后,作相应的修改:
GetAll()方法:
Comment out的代码,就是移到父类的代码,需要修改方法以及修饰符为Protected,这因为只是想让继承这个父类的类访问到即可。而刚才上面属性,还是保持原样“public”。
由于存储程名称在每个类别都不一样,因此方法名改为带一个参数的方法。
Insert()方法:
重构之后,Insert()方法被改为带三个参数的方法,这样解决传入主键,主键值,以及Insert的存储过程不一样的问题。
同样,Update():
Ok,我们的父类重构好了:
using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Web; /// <summary> /// Summary description for BaseUnitCode /// </summary> namespace Insus.NET { public class BaseUnitCode { private string _Description; private string _CreateBy; private string _UpdateBy; public string Description { get { return _Description; } set { _Description = value; } } public string CreateBy { get { return _CreateBy; } set { _CreateBy = value; } } public string UpdateBy { get { return _UpdateBy; } set { _UpdateBy = value; } } BusinessBase objBusinessBase = new BusinessBase(); public BaseUnitCode() { // // TODO: Add constructor logic here // } protected DataTable GetAll(string procedureName) { return objBusinessBase.GetDataToDataSet(procedureName).Tables[0]; } protected void Insert(string paramName, string paramValue, string procedurename) { Parameter[] parameter = { new Parameter (paramName,SqlDbType.NVarChar,-1,paramValue), new Parameter ("@Description",SqlDbType.NVarChar,-1,_Description), new Parameter ("@CreateBy",SqlDbType.NVarChar,-1,_CreateBy) }; objBusinessBase.ExecuteProcedure(procedurename, parameter); } protected void Update(string paramName, string paramValue, string procedurename) { Parameter[] parameter = { new Parameter (paramName,SqlDbType.NVarChar,-1,paramValue), new Parameter ("@Description",SqlDbType.NVarChar,-1,_Description), new Parameter ("@UpdateBy",SqlDbType.NVarChar,-1,_UpdateBy) }; objBusinessBase.ExecuteProcedure(procedurename, parameter); } protected void Delete(string paramName, string paramValue, string procedurename) { Parameter[] parameter = { new Parameter (paramName,SqlDbType.NVarChar,-1,paramValue) }; objBusinessBase.ExecuteProcedure(procedurename, parameter); } } }
接下来,我们需要在每一个UnitCode1.cs至UnitCode4.cs分别继承这个父类,下面只演法UnitCode1.cs,其余的UnitCode2至UnitCode4参考就是了。
下面是重构好的类别:
using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Web; /// <summary> /// Summary description for UnitCode1 /// </summary> namespace Insus.NET { public class UnitCode1 : BaseUnitCode { private string _Unit1; public string Unit1 { get { return _Unit1; } set { _Unit1 = value; } } public UnitCode1() { // // TODO: Add constructor logic here // } public DataTable GetAll() { return GetAll("usp_UnitCode1_GetAll"); } public void Insert() { Insert("@Unit1", _Unit1, "usp_UnitCode1_Insert"); } public void Update() { Update("@Unit1", _Unit1, "usp_UnitCode1_Update"); } public void Delete() { Delete("@Unit1", _Unit1, "usp_UnitCode1_Delete"); } } }
using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Web; /// <summary> /// Summary description for UnitCode1 /// </summary> namespace Insus.NET { public class UnitCode2 : BaseUnitCode { private string _Unit2; public string Unit2 { get { return _Unit2; } set { _Unit2 = value; } } public UnitCode2() { // // TODO: Add constructor logic here // } public DataTable GetAll() { return GetAll("usp_UnitCode2_GetAll"); } public void Insert() { Insert("@Unit2", _Unit2, "usp_UnitCode2_Insert"); } public void Update() { Update("@Unit2", _Unit2, "usp_UnitCode2_Update"); } public void Delete() { Delete("@Unit2", _Unit2, "usp_UnitCode2_Delete"); } } }
using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Web; /// <summary> /// Summary description for UnitCode3 /// </summary> namespace Insus.NET { public class UnitCode3 : BaseUnitCode { private string _Unit3; public string Unit3 { get { return _Unit3; } set { _Unit3 = value; } } public UnitCode3() { // // TODO: Add constructor logic here // } public DataTable GetAll() { return GetAll("usp_UnitCode3_GetAll"); } public void Insert() { Insert("@Unit3", _Unit3, "usp_UnitCode3_Insert"); } public void Update() { Update("@Unit3", _Unit3, "usp_UnitCode3_Update"); } public void Delete() { Delete("@Unit3", _Unit3, "usp_UnitCode3_Delete"); } } }
using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Web; /// <summary> /// Summary description for UnitCode1 /// </summary> namespace Insus.NET { public class UnitCode4 : BaseUnitCode { private string _Unit4; public string Unit4 { get { return _Unit4; } set { _Unit4 = value; } } public UnitCode4() { // // TODO: Add constructor logic here // } public DataTable GetAll() { return GetAll("usp_UnitCode4_GetAll"); } public void Insert() { Insert("@Unit4", _Unit4, "usp_UnitCode4_Insert"); } public void Update() { Update("@Unit4", _Unit4, "usp_UnitCode4_Update"); } public void Delete() { Delete("@Unit4", _Unit4, "usp_UnitCode4_Delete"); } } }
类别重构完成,接下来,我们对UnitCode1.aspx.cs至UnitCode4.aspx.cs进行重构。因为这四个页面的类,也有很多相同的代码。重构之前,先创建一个页面的基类,暂叫它为BasePage,此类别继承了
每个.aspx.cs继承刚才写的BasePage类,把InsusJsUtility objInsusJsUtility = new InsusJsUtility(); 这句拿掉,并移至BasePage中,根据继承的精神,它足够条件移了。
移至BasePage之后,需要添加修饰符protected,这样每个.aspx.cs才可以访问得到。
using System; using System.Collections.Generic; using System.Linq; using System.Web; /// <summary> /// Summary description for BasePage /// </summary> namespace Insus.NET { public class BasePage : System.Web.UI.Page { protected InsusJsUtility objInsusJsUtility = new InsusJsUtility(); } }
接下来,我们眼睛注意到每个.aspx.cs的Data_Binding()方法中,均有此一句:
((ISetValable)this.Master).SetValue("单位码X");
然后在每个.aspx.cs中,拿掉((ISetValable)this.Master).SetValue("单位码X"); 这句,并改为如下图高亮语句。
到此为止,Insus.NET暂停对.cs代码重构,转而看到Html代码。如下图中的插入记录代码,在四处个网页中每个.aspx都是相同的,因此Insus.NET对这些重构。
对这部分的重构,只有创建用户控件(ascx),然后搬移过去,完成之后,再把这个用户控件拉至网页.aspx中:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="InsertForm.ascx.cs" Inherits="Sys_InsertForm" %> <table class="table"> <tr class="tableRow"> <td class="tableCell" style="width: 35%;">单位码</td> <td class="tableCell">说明</td> <td style="width: 12%; text-align: center;" class="tableCell">操作</td> </tr> <tr> <td class="tableCell"> <asp:TextBox ID="TextBoxUnitCode" runat="server" CssClass="textbox" BackColor="#ffff6f"></asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ControlToValidate="TextBoxUnitCode" Display="none" ErrorMessage="单位码必须填写。" ValidationGroup="GeneralInsert"></asp:RequiredFieldValidator> </td> <td class="tableCell"> <asp:TextBox ID="TextBoxDescription" runat="server" CssClass="textbox"></asp:TextBox> </td> <td style="width: 12%;" class="tableCell"> <asp:ValidationSummary ID="ValidationSummary1" runat="server" EnableClientScript="true" ShowMessageBox="true" ShowSummary="false" ValidationGroup="GeneralInsert" /> <asp:Button ID="ButtonCreate" runat="server" OnClick="ButtonCreate_Click" Text="创建" ValidationGroup="GeneralInsert" /> </td> </tr> </table>
由于每个.aspx创建事件不一样,为了保持原有在.aspx.cs的事件,Insus.NET决定在用户控件中再public Click 事件。
上图中,还写两个属性,分别是两个文本框的属性,这是为了让.aspx还能与用户控件的两个文本框的交互。
用户控件重构好之后,当然需要拉至网页中去,有一个地方是需要注意的,在用户控件,还要写上OnClick事件,OnClick="ButtonCreate_Click":
在.aspx.cs代码页中,一些代码需要异动,参考下图高亮位置:
改程序,就得一步一个脚印,现在我们把目光放在每个.aspx的GridView控件上,它是显示记录,编辑记录以及删除等功能集成。有很高的相似度。只是Gridview的ID,DataKeyNames,以及OnRowEditing,OnRowCancelingEdit,OnRowUpdating,OnRowDeleting事件名称不一样,最后是绑定主键时,也不一样:
<ItemTemplate> <%# Eval("UnitX") %> </ItemTemplate>
这部分重构,相似度,但好象又很具有独立性,无法分开。现在Insus.NET决定对这些ID以及事件名改为一样。删除箭头所指的数字:
改完之后,如下代码一样,Insus.NET只列了个网页,如Unitcode4(部分):
<asp:GridView ID="GridViewUnitCode" runat="server" DataKeyNames="Unit4" AutoGenerateColumns="false" ShowHeader="false" CellPadding="2" CellSpacing="0" Width="100%" BorderWidth="1px" BorderColor="#c0c0c0" BorderStyle="solid" HeaderStyle-Height="25" RowStyle-Height="25" HeaderStyle-BackColor="#efebde" OnRowEditing="GridViewUnitCode_RowEditing" OnRowCancelingEdit="GridViewUnitCode_RowCancelingEdit" OnRowUpdating="GridViewUnitCode_RowUpdating" OnRowDeleting="GridViewUnitCode_RowDeleting"> <Columns> <asp:TemplateField> <ItemStyle BorderStyle="solid" BorderWidth="1px" BorderColor="#c0c0c0" Width="35%" /> <ItemTemplate> <%# Eval("Unit4") %> </ItemTemplate> </asp:TemplateField>
当然在每个UnitCode1~3.aspx.cs的事件中,也应该修改,参考下图,把箭头的数据全删除。
重名命重构,往往改动的地方都较多。全部改完之后,所有UnitCode1~4.aspx只差下图高亮位置的差异了:
怎样解决这些差异,它是一个表字段,而且是主键。动态产生或是加载是否可行,想到了,行动就是了。在GridView中,去掉DataKeyNames="Unit1"属性。在显示ItemTemple中,改为一个标签,并为GridView添加一个事件 OnRowDataBound="GridViewUnitCode_RowDataBound"。
在.aspx.cs中,添加一个变量,四个网页的变量值不同,分别为
string _DataKeyName = "Unit1";
string _DataKeyName = "Unit2";
string _DataKeyName = "Unit3";
string _DataKeyName = "Unit4";
参考下面动画:
程序经此一改,每个页面的html又一样了。所以我们可以把其中一页的GridView html代码块搬移至一个用户控件之内。创建一个用户控件:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="OperationForm.ascx.cs" Inherits="Sys_OperationForm" %> <asp:GridView ID="GridViewUnitCode" runat="server" AutoGenerateColumns="false" ShowHeader="false" CellPadding="2" CellSpacing="0" Width="100%" BorderWidth="1px" BorderColor="#c0c0c0" BorderStyle="solid" HeaderStyle-Height="25" RowStyle-Height="25" HeaderStyle-BackColor="#efebde" OnRowEditing="GridViewUnitCode_RowEditing" OnRowCancelingEdit="GridViewUnitCode_RowCancelingEdit" OnRowUpdating="GridViewUnitCode_RowUpdating" OnRowDeleting="GridViewUnitCode_RowDeleting" OnRowDataBound="GridViewUnitCode_RowDataBound"> <Columns> <asp:TemplateField> <ItemStyle BorderStyle="solid" BorderWidth="1px" BorderColor="#c0c0c0" Width="35%" /> <ItemTemplate> <asp:Label ID="LabelUnitCode" runat="server" Text=""></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateField> <ItemStyle BorderStyle="solid" BorderWidth="1px" BorderColor="#c0c0c0" /> <ItemTemplate> <%# Eval("Description") %> </ItemTemplate> <EditItemTemplate> <asp:TextBox ID="TextBoxDescription" runat="server" Text='<%# Eval("Description") %>' CssClass="textbox"></asp:TextBox> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField> <ItemStyle BorderStyle="solid" BorderWidth="1px" BorderColor="#c0c0c0" Width="8%" /> <ItemTemplate> <asp:Button ID="ButtonEdit" runat="server" Text="编辑" CommandName="Edit" CausesValidation="false" /> </ItemTemplate> <EditItemTemplate> <asp:ValidationSummary ID="ValidationSummary2" runat="server" EnableClientScript="true" ShowMessageBox="true" ShowSummary="false" ValidationGroup="GrieviewUpdate" /> <asp:Button ID="ButtonUpdate" runat="server" Text="更新" CommandName="Update" ValidationGroup="GrieviewUpdate" /> <asp:Button ID="ButtonCancel" runat="server" Text="取消" CommandName="Cancel" CausesValidation="false" /> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="删除"> <ItemStyle BorderStyle="solid" BorderWidth="1px" BorderColor="#c0c0c0" Width="4%" /> <ItemTemplate> <asp:Button ID="ButtonDelete" runat="server" Text="删除" CommandName="Delete" CausesValidation="false" /> <ajaxToolkit:ConfirmButtonExtender ID="ConfirmButtonExtender1" runat="server" TargetControlID="ButtonDelete" ConfirmText="确认删除记录?"> </ajaxToolkit:ConfirmButtonExtender> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView>
在OperationForm.ascx.cs中,需要处理几个问题,一是GridView的事件,主键,还是数据绑定的问题等。现在Insus.NET先解决GridView事件:
宣告几个事件,除了GridViewUnitCode_RowDataBound(object sender, GridViewRowEventArgs e)无需处理,因为它与页没有任何交互,只是为GridView显示数据而已。
下面是我们要解决主键,数据绑定,还有在GrieView显示主键的问题,在用户控件中,写一个只写属性,因为只需要为用户控件写入属性,不必为从用户控件获取值。
private string _DataKeyName; public string DataKeyName { set { _DataKeyName = value; } }
接下来,我们又需要去到每一个.aspx.cs中,为刚才的写好的属性赋值,你将看到下图高亮代码行,这样子,在每个网页运行时,就把网页的主键字符名称传至用户控件内。
处理Gridview显示主键时,把下面的方法全搬至用户控件中,其余网页相同的事件删除。
protected void GridViewUnitCode_RowDataBound(object sender, GridViewRowEventArgs e) { if (e.Row.RowType != DataControlRowType.DataRow) return; var drv = e.Row.DataItem as DataRowView; if (e.Row.FindControl("LabelUnitCode") != null) { var lbl = e.Row.FindControl("LabelUnitCode") as Label; lbl.Text = drv[_DataKeyName].ToString(); } }
为了让.aspx.cs能与用户控件更好的交互,需要在站点创建一个接口:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI.WebControls; /// <summary> /// Summary description for IGridViewControlable /// </summary> namespace Insus.NET { public interface IGridViewControlable { GridView GetGridViewControl(); } }
在用户控件OperationForm.ascx.cs实作它。
在每一个.aspx.cs代码页中,写一个get属性:
GridView GridViewUnitCode { get { return ((IGridViewControlable)this.OperationForm1).GetGridViewControl(); } }
写到此,还没有完毕,精彩还在后头。我们再写一个基类,此基将为前面写好的两个用户控件继承。写用户控件的基类,跟页面的基类完全一样。看到否,这基类是继承了System.Web.UI.UserControl:
基类写好,去分别打开以前写好的两个用户控件,继承这个用户控件的基类:
然后把在InsertForm.ascx.cs中下面代码移至BaseUserControl.cs控件中:
public event EventHandler Click; protected void ButtonCreate_Click(object sender, EventArgs e) { if (Click != null) { Click(this, e); } }
相同的情况,把OperationForm.cs中下面代码也移至BaseUserControl.cs控件中,移至之后,需要在这个基类的用户控件中,引用命名空间 using System.Web.UI.WebControls;
public event GridViewEditEventHandler RowEditing; public event GridViewCancelEditEventHandler RowCancelingEdit; public event GridViewUpdateEventHandler RowUpdating; public event GridViewDeleteEventHandler RowDeleting; protected void GridViewUnitCode_RowEditing(object sender, GridViewEditEventArgs e) { if (RowEditing != null) RowEditing(sender, e); } protected void GridViewUnitCode_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e) { if (RowCancelingEdit != null) RowCancelingEdit(sender, e); } protected void GridViewUnitCode_RowUpdating(object sender, GridViewUpdateEventArgs e) { if (RowUpdating != null) RowUpdating(sender, e); } protected void GridViewUnitCode_RowDeleting(object sender, GridViewDeleteEventArgs e) { if (RowDeleting != null) RowDeleting(sender, e); }
也就是说,当多用户控件中共用的属性,方法或是函数,也可以写在基类中。
接下来,我们还看到每个.aspx.cs还在一段相同的代码:
GridView GridViewUnitCode { get { return ((IGridViewControlable)this.OperationForm1).GetGridViewControl(); } }
Insus.NET也想把它移至基类BasePage.cs中去,直接cut and paste,它会提示找不到this.OperationForm1这个物件。不管怎样,出错就出错,移过去再说,其余的删除。解决问题,还是使用接口吧:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; /// <summary> /// Summary description for IUserControlable /// </summary> namespace Insus.NET { public interface IUserControlable { UserControl GetUserControl(); } }
然后每个UnitCode1~4.aspx.cs均实作这个接口,下仅在一个类演示:
我们打开BasePage基类,看下动画,很简单把刚才找不到物件的问题解决了:
ok,此博文到此为止,望看过的网友,能从中学习或温习到继承知识,了解到类别与父类,网页与基类页,用户控件与用户控件基类,还有的是网页与用户控件之间的交互通讯等。
最终重构好的程序,可以下载与博文开头的原程序对比。
http://download.cnblogs.com/insus/ASPDOTNET/Ref_Org_inhert.rar