ASP.NET MVC框架下添加菜单栏及分页项目

       原创声明:本文为作者原创,转载请注明出处:http://www.cnblogs.com/DrizzleWorm/p/7274866.html ,谢谢!

       我是做前端开发的,之前用C#的三层架构(UI、BLL、DAL)做过一个网站,这是我第一次接触ASP.NET MVC框架,首先给大家分享别人整理的ASP.NET MVC框架的一组教程:http://www.cnblogs.com/powertoolsteam/archive/2015/08/13/4667892.html内容很齐全,我是在先看了其他的教程,做出一点东西后感觉思路还是不清晰,对整个项目的数据流还没有完全理解的情况下,看了这组教程里的前几篇,结合我已经做出来的部分东西,对整个项目的数据流向更加清楚了,后面做起来就得心应手了。

一、项目背景及需求

       我实习的公司用的ERP表单共有五个模块:可填写表单、待发起表单、发起的表单、已处理表单和待处理表单。其中可填写表单页由两个部分组成:左侧菜单栏和右侧主要数据显示部分,该部分包括一个表格和一个分页。其他四个模块均只有主要数据显示部分,没有菜单栏。

       系统用久了之后,可填写的表单之外的另外几个模块的表单越来越多,要求在不更改后台代码的情况下,给发起的表单和另外两个模块添加左侧菜单栏。此处后台代码是指OA2.Core包,里面包含了所有Repository和用于分页的Pager的实现,只提供接口给前端访问。前端代码是指OA2.Web包,里面包含了利用MVC框架开发的前端代码,包括Models、Views、Controllers及路由配置文件等。因为添加菜单栏需要更改原来的sql查询语句,多连接一到两个表,另外Pager中一个很重要的类PagerInitializer被默认为protected,且没有提供相应的接口,因而在OA2.Web包中无法访问。所以不能照搬原来的代码,而需要把它单独作为一个模块重新写。

二、项目基本数据流(如图2.1所示)

图2.1 发起的表单最初的数据流

       发起的表单对应的动作方法MyStartedList()的源代码如下:

1 public ActionResult MyStartedList(int page,string s)
2 {
3     var url = Request.RawUrl;
4     if(page < 0) page = 1;
5     string html = string.Empty;
6     var viewModel = taskListRepository.GetSelfStartedTaskList(WebUser.Current.Id, url, s, out html, page);
7     ViewData["pager"] = html;
8     return View(viewModel);
9 }

       GetSelfStartedTaskList()方法的五个参数依次为:当前用户的id,访问路径,随访问路径传递给后台方法的参数,当前访问页的页码。

       思路与可填写表单的思路相同,但是在Repository的sql查询语句中,可填写的表单根据用户选择的表单类型查询数据库,而发起的表单的sql语句则直接查询了当前用户发起的所有类型的表单,无法选择特定类型的表单。

       因此,需要在MyStartedList.aspx视图中添加了表单分类菜单后,再根据用户选择的表单类型,用新的sql语句访问数据库中对应的数据,而不能再通过调用GetSelfStartedTaskList()方法得到需要的数据。用返回的数据实例化“发起的表单”对应的数据模型TaskSelfStarted的一个对象,再将其通过View返回给视图。

       需要注意的是,MyStartedList.aspx视图原本只有主要数据显示部分:一个table和其对应的分页,如图2.2所示,而现在它由两部分组成:一个表单分类菜单和主要数据显示部分,如图2.3所示,点击表单分类中的表单类型,table中的数据和对应的分页要刷新,而表单分类不用刷新。由于调用MyStartedList()会返回整个视图,而我需要的只是返回分部视图,因而想到重新写一个动作方法,使其返回一个PartialView. 在MyStartedList.aspx视图中,点击某个表单类型,用ajax发送请求调用新的动作方法,将其返回的数据填充到主要数据显示部分即可。新的动作方法为MyStarted(),返回的分部视图为MyStarted.ascx. 添加了表单分类的发起的表单的数据流如图2.4所示。

 

图2.2 原来的MyStartedList.aspx视图

 

图2.3 新的MyStartedList.aspx视图

 

图2.4 发起的表单新的数据流

三、分页问题

       原来的动作方法MyStartedList()调用的GetSelfStartedTaskList()方法包含对分页的处理,返回的视图里包含分页。新的动作方法MyStarted()暂时不包含对分页的处理,返回的视图里不包含分页。前面已经提到,已经封装好的OA2.Core包有一个Pager专门用于对数据进行分页,但其下一个很重要的类PagerInitializer的权限为默认的protected,且没有提供相应的接口,因而无法访问。要给分部视图MyStarted.ascx添加分页,只能自己写代码实现。

       常见的分页方法包括两种:假分页和真分页。关于真假分页可以参照下文的介绍:http://m.blog.csdn.net/jiuqiyuliang/article/details/18009515 .作者写的思路比较清晰,且有相应的代码实现。

       我尝试过上文作者写到的用C#自带的PagedDataSource实现假分页,以及用AspNetPager控件实现真分页(AspNetPager控件的原理、实现及示例的链接:http://www.webdiyer.com/aspnetpager),但都未成功。原因是在视图中需用DataList或Repeater等控件对后台传来的数据进行绑定,如:

1 <td><%#DataBinder.Eval(Container.DataItem,”orderid”)%></td>

       但是本项目中视图需要获取这些数据,并对它们进行相应的操作,如:

 1 <td>
 2   <%: Html.ActionLink("处理明细","History",new { instanceId = item.InstanceId })%>
 3   <%if (item.Status == "Cancelled")
 4     {
 5       using (Html.BeginForm("Activate","Flow"))
 6       {%>
 7           <%: Html.Hidden("fiid",item.InstanceId)%>
 8           <input type="submit" value="激活" />
 9       <%}
10       }%>
11 </td>

       如果用DataBinder.Eval()或其他方法绑定数据,会出现<% %>嵌套的问题,尤其是if语句所在行,<% %>嵌套无法正常绑定数据。我当时未能解决这个问题,也觉得这种方法相对麻烦,代码量较大,想尽可能地利用已有的代码。于是尝试自己写分页,思路是在MyStarted.ascx视图中添加分页组件和相应的事件处理程序,每点击一次页码(具体的数字或“上一页”、“下一页”)时,通过ajax请求方式将需要显示的页的页码传给动作方法MyStarted(),该方法根据页码查询数据库中对应的数据,再将其返回给视图MyStarted.ascx. 采用的是真分页的方法,每次只从数据库中取需要显示的数据。加了分页后的数据流如图3.1所示。

图4.1 发起的表单最终的数据流

四、源代码

1、动作方法MyStartedList()中添加了表单分类菜单后:

 1 public ActionResult MyStartedList(int page,string s)
 2 {
 3     //TableCategory()方法返回表单分类的数据模型TableCategory的一个实例,包含所有表单类型
 4     //将实例通过ViewData传递给MyStartedList.aspx
 5     IList<TableCategory> tablecategory = TableCategory();
 6     ViewData["TableCategory"] = tablecategory;
 7   
 8     var url = Request.RawUrl;
 9     if(page < 0) page = 1;
10     string html = string.Empty;
11     var viewModel = taskListRepository.GetSelfStartedTaskList(WebUser.Current.Id, url, s ,out html,page);
12     ViewData["pager"] = html;
13     return View(viewModel);
14 }
View Code

2、MystartedList.aspx视图的主要组成部分:

 1 <!-- 左侧表单分类,样式省略-->
 2 <div>
 3   <table id="category">
 4     <% foreach (var tableCategory in ViewData["TableCategory"] as IList<OA2.Core.Data.TableCategory>)
 5     {%>
 6     <tr>
 7       <td class="tt" id="<%tableCategory.Id%>">
 8         <%: tableCategory.Name%>
 9       </td>
10     </tr>
11     <%}%>
12   </table>    
13 </div>
14 
15 <!-- 右侧原来的表格及分页 -->
16 <div id="partial">
17   <table id="datalist">
18     <tr>
19       <th>...</th>
20       ...
21     </tr>
22     <!-- Model指MyStartedList.aspx继承的数据模型TaskSelfStarted的Model属性-->
23     <% foreach (var item in Model)
24     {%>
25     <tr>
26       <td><%: item.Subject%></td>
27       ...
28       <td>
29         <%: Html.ActionLink("处理明细","History",new { instanceId = item.InstanceId })%>
30         <%if (item.Status == "Cancelled")
31         {
32           using (Html.BeginForm("Activate","Flow"))
33           {%>
34             <%: Html.Hidden("fiid",item.InstanceId)%>
35             <input type="submit" value="激活" />
36          <%}
37         }%>
38       </td>
39     </tr>
40   </table>
41   <%=ViewData["pager"]%>
42 </div>
43 
44 <script type="text/javascript">
45   $(document).ready(function(){
46     $(".tt").click(function(){
47       //data中的各项参数均在MyStarted()中接收,且有相应的解释
48       //注意url和dataType
49       $.ajax({
50         type:"POST",
51         url:"MyStarted.aspx",
52         data:{
53           ttID:$(this).attr("id"),
54           page:1,
55           firstVisit:true,
56           rest:0,
57           last:false,
58           records:0
59         },
60         dataType:"html",
61         success:function(responseData){
62           $("#partial").html(responseData);
63         },
64         error:function(XMLHttpRequest, textStatus, errorThrown){
65           alert(XMLHttpRequest.readyState);
66           alert(XMLHttpRequest.status);
67         }
68       })
69     })
70   }
71 </script>
View Code

3、动作方法MyStarted():

 1 public ActionResult MyStarted()
 2 {
 3     //ttID指选择的表单类型对应的id,page指需要显示的页的页码
 4     //rest指最后一页的记录数,最后一页需要单独考虑,last指当前page是否为最后一页
 5     //firstVisit指是否是第一次访问MyStarted()方法,如果是从MyStartedList.aspx访问,则为第一次访问,否则不是第一次访问
 6     //sblength指总的记录数
 7     //这些参数中page是动态更新的,ttID和sblength在方法和视图之间传递主要是为了保存数据
 8     string ttID = Request.Params["ttID"].ToString();
 9     int page = Convert.ToInt32(Request.Params["page"]);
10     int rest = Convert.ToInt32(Request.Params["rest"]);
11     bool last = Convert.ToBoolean(Request.Params["last"]);
12     bool firstVisit = Convert.ToBoolean(Request.Params["firstVisit"]);
13     int sblength = Convert.ToInt32(Request.Params["records"]);
14 
15     int num = 0, end = 0;
16     //每页20条数据
17     //如果当前页为最后一页,且数据不足20条
18     {
19         num = rest;
20         end = (page - 1) * 20;
21     }
22     else
23     {
24         num = 20;
25         end = page * 20;
26     }
27     SqlCommandHelper flows = new SqlCommandHelper(ConnectionStringManager.FlowDBConnectionString);
28     IList<TaskSelfStarted> task = new IList<TaskSelfStarted>();
29     StringBuilder sb = new StringBuilder();
30     //从数据库中取第 (end-num+1) 到 end 之间的数据,共 num 条
31     //要对选择的数据依据时间从进到远的顺序排序,所以先按倒序选end条数据,再将这些数据按顺序排序,选num条,最后再将它们按倒序排序
32     sb.Append("select * from(select top " + num + " * from(select top " + end + " * from ... order by Time desc) as a order by Time asc) as a order by Time desc");
33     flows.SetSb(sb);
34     DataTable dt = flows.OpenDataTable();
35     DataRow dr = null;
36     if(dt.Rows.Count != 0)
37     {
38         for(int i = 0; i < dt.Rows.Count; i++){
39             dr = dt.Rows[i];
40             task.Add(new TaskSelfStarted(){
41                 FlowName = dr["FlowName"].ToString();
42                 ......
43             });
44         }
45     }
46     
47     //第一次访问MyStarted()方法时,从数据库中获取符合条件的记录的总数,并将其赋值给sblength,传递给MyStarted.ascx
48     if(firstVisit == true){
49         SqlCommandHelper flowstotal = new SqlCommandHelper(ConnectionStringManager.FlowDBConnectionString);
50         StringBuilder sbtotal = new StringBuilder();
51         sbtotal.Append("select count(*) from ...");
52         flowstotal.SetSb(sbtotal);
53         DataTable dttotal = flowstotal.OpenDataTable();
54         sblength = Convert.ToInt32(dttotal.Rows[0][0]);
55     }
56     //将记录数、当前页码和所选的表单类型的id传给MyStarted.ascx,用于下一次请求本方法
57     ViewData["total"] = sblength;
58     ViewData["curpage"] = page;
59     ViewData["ttID"] = ttID;
60     return PartialView("MyStarted", task);
61 }
View Code

4、MyStarted.ascx视图的主要组成部分:

  1 <div id="myartial">    
  2   <table id="datalist">
  3   <!-- 与MyStartedList.aspx中的table部分代码相同 -->
  4   </table>
  5 
  6   <div>
  7     <p>
  8     <span id="ttID" style="display:none"><%=ViewData["ttID"]%></span>
  9     <span id="currentPage" style="display:none"><%=ViewData["curpage"]%></span>
 10     <span id="pageNav"></span>&nbsp;
 11     </p>
 12     <p>
 13<a id="totalRecords"><%=ViewData["total"]%></a>条记录&nbsp;
 14<a id="totalPages"></a>&nbsp;
 15     </p>
 16   </div>
 17 </div>
 18 <script type="text/javascript">
 19   //获取总记录数
 20   var tr = document.getElementById("totalRecords");
 21   var totalRecords = parseInt(tr.innerHTML);
 22   //计算总页数,每页20条数据
 23   var tp = document.getElementById("totalPages");
 24   var totalPages = Math.ceil(totalRecords / 20);
 25   tp.innerHTML = totalPages;
 26   //计算最后一页的记录数
 27   var rest = totalRecords % 20;
 28   var ti = document.getElementById("ttID");
 29   var ttID = ti.innerHTML;
 30   //获取当前页数
 31   var cp = document.getElementById("currentPage");
 32   var cur = parseInt(cp.innerHTML);
 33   
 34   var pageNav = pageNav || {};
 35   //p为房钱页码,pn为总页数
 36   pageNav.nav = function(p,pn){
 37     if(pn <= 1){
 38       this.p = 1;
 39       this.pn = 1;
 40       var onepage = "<span>首页&nbsp;上一页&nbsp;[" + p + "]&nbsp;下一页&nbsp;末页</span>";
 41       return onepage;
 42     }
 43     if(pn < p){
 44       pn = p;
 45     }
 46     var re = "";
 47     //第一页
 48     if( p <= 1){
 49       p = 1;
 50     }else{
 51       re = re + this.pHtml(1, pn, "首页"); //总显示第一页
 52       re = re + this.pHtml(p - 1, pn, "上一页"); //非第一页
 53     }
 54     //校正页码
 55     this.p = p;
 56     this.pn = pn;
 57     //开始页码
 58     var start = 1;
 59     var end = (pn < 10)? pn: 10;
 60     if(p >= 6){
 61         if(pn <= pn - 4){
 62           start = pn - 5;
 63           end = pn + 4;
 64         }else{
 65           start = pn - 9;
 66           end = pn;
 67         }
 68     }
 69     for (var i = start; i < p; i++){
 70       re = re + this.pHtml(i, pn);
 71     } 
 72     re = re + this.pHtml2(p);
 73     for (var j = p + 1; j <= end; j++){
 74       re = re + this.pHtml(j, pn);
 75     }
 76     if(p < pn){
 77       re = re + this.pHtml(p + 1, pn, "下一页");
 78     }
 79     if(end < pn){
 80       re = re + this.pHtml(pn, pn, "末页");
 81     }
 82     return re;
 83   }
 84   //显示非当前页
 85   pageNav.pHtml = function(pageNo, pn, showPageNo){
 86     showPageNo = showPageNo || pageNo;
 87     var H = "&nbsp;<a href='javascript:pageNav.go(" + pageNo + "," + pn + ");'>" + showPageNo + "</a>&nbsp;";
 88     return H;
 89   }
 90   //显示当前页
 91   pageNav.pHtml2 = function(pageNo){
 92     var H = "<span>[" + pageNo + "]&nbsp;</span>";
 93     return H;
 94   }
 95   //输出页码
 96   pageNav.go = function(p, pn){
 97     if(this.fn != null){
 98       this.fn(p, pn);
 99     }
100   }
101   //转到页码
102   pageNav.fn = function(p, pn){
103     cur = p;
104     cp.innerHTML = cur;
105     var flag = false;
106     if(p == pn){
107       flag = true;
108     }
109     $.ajax({
110         type:"POST",
111         url:"MyStarted.aspx",
112         data:{
113           ttID:ttID,
114           page:p,
115           firstVisit:false,
116           rest:rest,
117           last:flag,
118           records:totalRecords
119         },
120         dataType:"html",
121         success:function(responseData){
122           $("#mypartial").html(responseData);
123         },
124         error:function(XMLHttpRequest, textStatus, errorThrown){
125           alert(XMLHttpRequest.readyState);
126           alert(XMLHttpRequest.status);
127         }
128       })
129   }
130   var pNav = document.getElementById("pageNav");
131   pNav.innerHTML = pageNav.nav(cur, totalPages);
132 </script>
View Code

       因为代码是在公司内网环境中写的,无法给出截图。文中代码全部为手敲的,难免有疏漏之处,各位在阅读的过程中如果发现错误,请在文后留言指正,我一定认真回复,谢谢!

posted @ 2017-08-02 22:49  DrizzleWorm  阅读(889)  评论(0编辑  收藏  举报