利用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),并在母版页的导航添加链接(如果你没有母版页可以跳过此步骤直接创建页面即可)

View Code
<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的页面代码如下:

View Code
<p>
    <asp:TextBox ID="txtKey" runat="server"></asp:TextBox>&nbsp;
    <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 {  }
View Code
    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数据,用于绑定“操作”列的下拉菜单。

View Code
    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

View Code
    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,如下图所示

其最终页面代码为:

View Code
<p>
    <asp:TextBox ID="txtKey" runat="server"></asp:TextBox>&nbsp;
    <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>

编写后台代码,这里我不再使用自动生成列,由我编码生成列,

View Code
    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。

View Code
        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,这是我自己定义的

View Code
    /// <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

posted @ 2012-08-11 18:02  dong.net  阅读(2244)  评论(0编辑  收藏  举报