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 }
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>
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 }
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> 11 </p> 12 <p> 13 共<a id="totalRecords"><%=ViewData["total"]%></a>条记录 14 共<a id="totalPages"></a>页 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>首页 上一页 [" + p + "] 下一页 末页</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 = " <a href='javascript:pageNav.go(" + pageNo + "," + pn + ");'>" + showPageNo + "</a> "; 88 return H; 89 } 90 //显示当前页 91 pageNav.pHtml2 = function(pageNo){ 92 var H = "<span>[" + pageNo + "] </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>
因为代码是在公司内网环境中写的,无法给出截图。文中代码全部为手敲的,难免有疏漏之处,各位在阅读的过程中如果发现错误,请在文后留言指正,我一定认真回复,谢谢!