利用override多态原理实现对相似页面的后台代码的抽象,并实现动态GridView动态列数据绑定
关于多态本人写过一篇随笔,您兴趣的可以看一下 http://www.cnblogs.com/FreeDong/archive/2012/08/07/2626312.html。
以下通过一个ASP.NET的Demo,希望能使您加深对多态的理解。
现在的需求是这样子(当然该需求是借助于最近的项目中碰到的问题),在该系统中的流程管理中,有两个页面,一个显示的是我本人发起的审批列表,另一个是等待我进行审批的列表,他们的查询以及列表显示和查看审批历史等均一致,唯一不同的是待审批还有一个可执行审批动作的一列,但是不同的人或者在不同的应用(我在这里假设该系统是有多个应用的复杂系统)里面获取到列表的列是不一样的,所以需要使用动态列,当然这些数据来源我在这里不赘述,为便于举例我也不会去使用到数据库,您可以构造一个类,包含一个属性为列的集合,一个属性为数据集即可,当然下面的例子均会涉及。
我的环境为Windows 7+ Visual Studio 2010,我创建了一个WebApplication,名为WebApplication3,为了页面好看点,我将不会选择空Web Application。添加两个aspx页面(MyApprovals和ToApprovals),并在母版页的导航添加链接(如果你没有母版页可以跳过此步骤直接创建页面即可)
<asp:Menu ID="NavigationMenu" runat="server" CssClass="menu" EnableViewState="false" IncludeStyleBlock="false" Orientation="Horizontal"> <Items> <asp:MenuItem NavigateUrl="~/Default.aspx" Text="主页"/> <asp:MenuItem NavigateUrl="~/MyApprovals.aspx" Text="我的申请"/> <asp:MenuItem NavigateUrl="~/ToApprovals.aspx" Text="待我审批"/> <asp:MenuItem NavigateUrl="~/About.aspx" Text="关于"/> </Items> </asp:Menu>
页面代码很简单,MyApprovals的页面代码如下:
<p> <asp:TextBox ID="txtKey" runat="server"></asp:TextBox> <asp:Button ID="btnSearch" runat="server" Text="Button" onclick="btnSearch_Click" /> <asp:Label ID="lblMsg" runat="server" ForeColor="Red"></asp:Label> </p> <div> <asp:GridView ID="gvApprovals" runat="server"> </asp:GridView> </div>
ToApprovals的页面代码后面再贴出来。
创建他们的后台代码的基类名为ApprovalBasePage,我们在实际开发中可能还有一个更BasePage之类的基类,这时ApprovalBasePage就应继承于BasePage,
public class BasePage : System.Web.UI.Page { }
public class ApprovalBasePage : BasePage { /// <summary> /// 0为我发起审批的页面,1为待审批的页面,可由子类去确定类型 /// </summary> protected virtual int PageType { get { return 0; } } private ApprovalData dataSource; /// <summary> /// 获取数据源 /// </summary> ApprovalData DataSource { get { if (this.dataSource == null) { this.dataSource = ApprovalData.GetData(this.PageType); } return this.dataSource; } } /// <summary> /// 进行数据绑定,参数仅作为示范作用, /// 因为绑定时可能会与用户有些交互,这些交互很可能不相同,因而留给子类去重写 /// 但是本例中他们的数据绑定几乎是一样的 /// </summary> protected virtual void BindData(string key = ""){ } /// <summary> /// 将数据转化为DataTable,便于绑定 /// </summary> /// <returns></returns> protected DataTable DataToTable() { DataTable dt = new DataTable(); if (this.DataSource == null) { return null; } foreach (var item in this.DataSource.HeadList) { dt.Columns.Add(item, typeof(string)); } DataRow dr = null; int colIndex = 0; foreach (var item in this.DataSource.ContentList) { dr = dt.NewRow(); colIndex = 0; foreach (var itemCol in item) { dr[colIndex++] = itemCol; if (colIndex >= dt.Columns.Count) { //虽然提供该接口的保证数据肯定是与列数对应的 //但我还是尽量保证数据异常时程序不会报错 break; } } dt.Rows.Add(dr); } return dt; } #region event 两个页面的共同事件都放进这里来了 protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { BindData(); } } protected void btnSearch_Click(object sender, EventArgs e) { BindData(); } #endregion }
我这里的BasePage什么都不做(注意,我并不喜欢把Web项目的C#程序写在App_code文件夹,因为我从我刚开始接触ASP.NET我就讨厌它)。还有,您会发现ApprovalBasePage类中的数据来源来自于类ApprovalData,这只是我构造出来的提供数据的类,那么在实际需求当中它可能是有更底层的接口实现的,并且这个接口可能都不是由我们来编写,我甚至都可以不用管这些数据,因为可能其逻辑是极其复杂,我只需要知道它给我提供的数据集的结构即可,这就是面向对象的抽象思维,提供下这个类的代码,其中有json数据,用于绑定“操作”列的下拉菜单。
public class ApprovalData { /// <summary> /// 数据列表头部集合 /// </summary> public List<string> HeadList { get; set; } /// <summary> /// 包含的数据集,以行为里层集合,外层的集合为行的集合 /// </summary> public List<List<string>> ContentList { get; set;} public ApprovalData() { this.HeadList = new List<string>(); this.ContentList = new List<List<string>>(); } /// <summary> /// 模拟返回数据的方法,这里构造的是写死的数据 /// </summary> /// <param name="dataType">0为我发起的审批数据,1为待我审批的数据</param> /// <returns></returns> public static ApprovalData GetData(int dataType) { ApprovalData model = new ApprovalData(); model.HeadList.AddRange(new string[]{"标题","发起时间","状态"}); if (dataType == 1) { model.HeadList.Add("操作"); model.ContentList.AddRange( new List<string>[] { new List<string> { "项目启动申请","2012-08-01","已提交","[{key:2,value:'同意'},{key:3,value:'拒绝'}]" }, new List<string> { "请批准报销","2011-12-23","已提交","[{key:4,value:'数据有错'},{key:4,value:'通过并提交给总监'}]" } }); } else { model.ContentList.AddRange( new List<string>[] { new List<string> { "请批准经费","2012-08-01","未提交" }, new List<string> { "请假申请","2011-12-23","已批准" } }); } return model; } }
OK他们的基类准备完毕,接下来编写MyApprovals.aspx.cs
public partial class MyApprovals : ApprovalBasePage { protected override void BindData(string key = "") { DataTable dt = DataToTable(); if (dt == null) { this.lblMsg.Text = "No Record"; return; } this.gvApprovals.DataSource = dt; this.gvApprovals.DataBind(); } }
好了,先浏览下这个页面的效果:
很难看哦,很抱歉我可没有任何美化能力,如果您需要美化她,可以通过css。这个页面正常,接下来还有个难点在于待审批页面。ToApprovals页面的Gridview中有一列,是操作,表示我可以进行的操作,我在这里只示范绑定效果,提交操作的程序,如果您需要,我认为问题不大。
首先为ToApprovals的GridView添加一个模板列,页眉为“操作”,转到编辑模板页,添加DropDownList和HiddenField,并绑定HiddenField,如下图所示
其最终页面代码为:
<p> <asp:TextBox ID="txtKey" runat="server"></asp:TextBox> <asp:Button ID="btnSearch" runat="server" Text="Button" onclick="btnSearch_Click" /> <asp:Label ID="lblMsg" runat="server" ForeColor="Red"></asp:Label> </p> <div> <asp:GridView ID="gvApprovals" runat="server" AutoGenerateColumns="False" onrowdatabound="gvApprovals_RowDataBound"> <Columns> <asp:TemplateField HeaderText="操作"> <ItemTemplate> <asp:DropDownList ID="DropDownList1" runat="server"> </asp:DropDownList> <asp:HiddenField ID="hdf" runat="server" Value='<%# Eval("操作") %>' /> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> </div>
编写后台代码,这里我不再使用自动生成列,由我编码生成列,
public partial class ToApprovals : ApprovalBasePage { protected override int PageType { get { return 1; } } protected override void BindData(string key = "") { DataTable dt = DataToTable(); if (dt == null) { this.lblMsg.Text = "No Record"; return; } this.gvApprovals.DataSource = dt; string head = string.Empty; int count = dt.Columns.Count; //因为模板列已存在,所以要进行倒序插入第一列 for (int i = count - 1; i >= 0; i--) { var item = dt.Columns[i].ColumnName; //注意这一项我们约定好是操作,如可变性大,可以写在配置文件 if (item == "操作" || item == null) { //因为该列已写在模板列,所以不在这里生成列 continue; } BoundField field = new BoundField(); field.HeaderText = item; field.DataField = item; this.gvApprovals.Columns.Insert(0, field); } this.gvApprovals.DataBind(); } }
完成以上代码编写后运行,页面已正常显示,但是操作里面的下拉菜单还是空的,没有数据,这里我还要在做一件事,就是绑定其,我们要使用到GridView的RowDataBound事件,该事件代码如下,添加至ToApprovals.aspx.cs。
protected void gvApprovals_RowDataBound(object sender, GridViewRowEventArgs e) { HiddenField hdf = e.Row.FindControl("hdf") as HiddenField; DropDownList ddl = e.Row.FindControl("DropDownList1") as DropDownList; if (hdf == null || string.IsNullOrEmpty(hdf.Value) || ddl == null) { return; } JavaScriptSerializer jss = new JavaScriptSerializer(); KeyValue[] obj = jss.Deserialize<KeyValue[]>(hdf.Value); ddl.DataSource = obj; ddl.DataTextField = "value"; ddl.DataValueField = "key"; ddl.DataBind(); }
还有一个数据类型是KeyValue,这是我自己定义的
/// <summary> /// 类似键值对,用于绑定 /// </summary> public struct KeyValue { public string key { get; set; } public string value { get; set; } }
然后运行效果
这里可能有人问了,为什么不用Dictionary,原因是我提供的json数据不能直接转化为该类型,我们一般解析json格式的字符串时,定义一个类或者结构,然后在转化,如上代码所示,很简单吧?是的,当人的思想上升到一定境界之后不管碰到什么问题,我们总会找到解决方案,编程更是如此,没有满足不了的需求,只有我们还没想到解决方案。
其实在页面的实现方式上还有一个实现方式,即嵌套循环显示列表,这种方式也有其便捷之处,在ASP.NET MVC下可以使用该方法。本例主要也是为了讲解下动态使用GridView的方式,当然您也可以直接用C#编程生成有一个Gridview,然后在添加到页面中。
好累丫,终于写完,感觉我上面讲的好乱啊,如果有幸能让您一直看完,我对您表示衷心的感谢。还是附上源码吧 http://download.csdn.net/detail/dongdong22014/4492932