首页 > 代码库 > ASP.NET MVC框架下添加菜单栏及分页项目

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视图的主要组成部分:

 

<div id="myartial">    
  <table id="datalist">
  <!-- 与MyStartedList.aspx中的table部分代码相同 -->
  </table>

  <div>
    <p>
    <span id="ttID" style="display:none"><%=ViewData["ttID"]%></span>
    <span id="currentPage" style="display:none"><%=ViewData["curpage"]%></span>
    <span id="pageNav"></span>&nbsp;
    </p>
    <p><a id="totalRecords"><%=ViewData["total"]%></a>条记录&nbsp;<a id="totalPages"></a>&nbsp;
    </p>
  </div>
</div>
<script type="text/javascript">
  //获取总记录数
  var tr = document.getElementById("totalRecords");
  var totalRecords = parseInt(tr.innerHTML);
  //计算总页数,每页20条数据
  var tp = document.getElementById("totalPages");
  var totalPages = Math.ceil(totalRecords / 20);
  tp.innerHTML = totalPages;
  //计算最后一页的记录数
  var rest = totalRecords % 20;
  var ti = document.getElementById("ttID");
  var ttID = ti.innerHTML;
  //获取当前页数
  var cp = document.getElementById("currentPage");
  var cur = parseInt(cp.innerHTML);
  
  var pageNav = pageNav || {};
  //p为房钱页码,pn为总页数
  pageNav.nav = function(p,pn){
    if(pn <= 1){
      this.p = 1;
      this.pn = 1;
      var onepage = "<span>首页&nbsp;上一页&nbsp;[" + p + "]&nbsp;下一页&nbsp;末页</span>";
      return onepage;
    }
    if(pn < p){
      pn = p;
    }
    var re = "";
    //第一页
    if( p <= 1){
      p = 1;
    }else{
      re = re + this.pHtml(1, pn, "首页"); //总显示第一页
      re = re + this.pHtml(p - 1, pn, "上一页"); //非第一页
    }
    //校正页码
    this.p = p;
    this.pn = pn;
    //开始页码
    var start = 1;
    var end = (pn < 10)? pn: 10;
    if(p >= 6){
        if(pn <= pn - 4){
          start = pn - 5;
          end = pn + 4;
        }else{
          start = pn - 9;
          end = pn;
        }
    }
    for (var i = start; i < p; i++){
      re = re + this.pHtml(i, pn);
    } 
    re = re + this.pHtml2(p);
    for (var j = p + 1; j <= end; j++){
      re = re + this.pHtml(j, pn);
    }
    if(p < pn){
      re = re + this.pHtml(p + 1, pn, "下一页");
    }
    if(end < pn){
      re = re + this.pHtml(pn, pn, "末页");
    }
    return re;
  }
  //显示非当前页
  pageNav.pHtml = function(pageNo, pn, showPageNo){
    showPageNo = showPageNo || pageNo;
    var H = "&nbsp;<a href=‘javascript:pageNav.go(" + pageNo + "," + pn + ");‘>" + showPageNo + "</a>&nbsp;";
    return H;
  }
  //显示当前页
  pageNav.pHtml2 = function(pageNo){
    var H = "<span>[" + pageNo + "]&nbsp;</span>";
    return H;
  }
  //输出页码
  pageNav.go = function(p, pn){
    if(this.fn != null){
      this.fn(p, pn);
    }
  }
  //转到页码
  pageNav.fn = function(p, pn){
    cur = p;
    cp.innerHTML = cur;
    var flag = false;
    if(p == pn){
      flag = true;
    }
    $.ajax({
        type:"POST",
        url:"MyStarted.aspx",
        data:{
          ttID:ttID,
          page:p,
          firstVisit:false,
          rest:rest,
          last:flag,
          records:totalRecords
        },
        dataType:"html",
        success:function(responseData){
          $("#mypartial").html(responseData);
        },
        error:function(XMLHttpRequest, textStatus, errorThrown){
          alert(XMLHttpRequest.readyState);
          alert(XMLHttpRequest.status);
        }
      })
  }
  var pNav = document.getElementById("pageNav");
  pNav.innerHTML = pageNav.nav(cur, totalPages);
</script>

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

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