[jQuery] jQuery DataTables Server Side模式整合ASP.net MVC 的查詢、分頁和排序 Part2

jQuery Datatables 1.10+ & ASP.NET MVC with Server Side Integration

前言

工作上使用jQuery DataTables 套件時,個人比較推薦採用Server Side模式開發

一啟用Server Side模式後,當用戶點擊分頁、排序時,DataTables會自動向Server端發出Ajax,並自帶一些參數↓

服务器处理(server-side) 手册 Datatables中文网 也有ServerSide模式的詳細說明

以下是啟用Server Side模式的範例,serverSide:true,並且同時要給ajax參數,否則會報錯

不過DataTables如此的設定,會造成畫面第一次載入時,就會自動向Server端發出Ajax,某方面來說是個效能問題

參考此文章:Disable initial automatic ajax call - DataTable server side paging

還須要加入一個參數:"deferLoading": 0

中文文件說明:deferLoading 延迟服务器加载数据直到第二次绘制 

2018.4.9追記:雖然本範例使用Ajax Get Method來送資料給Server端,但如果畫面上的資料行或查詢條件一多的話,有可能碰上HTTP Error 404.15 - Not Found 送給Server端的QueryString太長的問題

解決辦法兩種:

1.DataTable發出的Ajax改用Post Method

2.或更改Web.config設定 How to configure the web.config to allow requests of any length

 

實作

1.在畫面上放置一個表單,當做查詢條件使用

2.放置DataTables的表格和資料行名稱

3.初始化DataTable()時,設定以下參數:

serverSide:true (啟用ServerSide模式)、

searching: false 關閉filter功能,因為工作上大都用不到 (官網 searching 文件)

deferLoading: 0 (畫面一開始載入不發出ajax撈資料)

processing: true會讓讀取資料時,出現「Processing...」字眼,個人覺得滿醜所以拿掉它XD(我習慣採用jQuery blockUI 下一篇文章會談到如何使用自己的Loading效果)

columns設定DataBinding的資料行名稱

設定ajax參數(和jQuery的$.ajax()裡的參數名稱幾乎一樣),須留意ajax的data參數要用function的方式設定,如此每次發出Ajax時才會重新抓表單裡的輸入值

參考:DataTables.js → How to update your data object for AJAX JSON data retrieval 和 DataTables Server-side Processing with Custom Parameters in CodeIgniter

2018.4.5 追記:如果畫面上的查詢條件很多的話,ajax裡的data參數不就一行一行寫到天荒地老XD?,為了減少程式碼個人推薦使用jQuery extend()搭配jQuery serializeObject();套件 

可以改寫如下

※提醒:和原生的jQuery serialize()方法有所不同,jQuery serializeObject套件並不會把中文的值做UrlEncode,它仍會保留原本中文的值

※目前已知此套件遇到type="email"的欄位,無法序列化為物件的Property,把type="email"改成type="text"即可

但和原生jQuery serialize()同樣的是,當checkbox沒勾時,序列化後的結果並沒有checkbox的name和value,如此傳遞到Server端就會是null

   ajax: {
           method:"get",
           url: "@Url.Action("GetData_Full", "Home")",
           data: function (d)
           {//↓新寫法
            let formObj =  $("form[name=myForm]").serializeObject();
            //額外處理checkbox(因為沒勾選時,會是null)
            $("form[name=myForm] input:checkbox").each(function (i, chk) {
                formObj[chk.name] = chk.checked;//chekcbox是否勾選
            });
              //↓寫一行就好
              $.extend(true, d , formObj);
                                
           }
   }

設定order參數預設哪個資料行排序,參考官網:Default ordering (sorting)

order參數沒設定的話,預設值為:order: [[0, "asc"]]

或參考此文章關閉一開始資料的排序( order:[] ):Is there a way to disable initial sorting for jquery DataTables?

↑會有這需求,通常為要排序的資料行不在畫面上,而由Server端決定,例如:異動時間

關閉orderMulti參數("orderMulti": false),因為會增加排序處理複雜度、資料處理時間:orderMulti

4.最後表單的按鈕click時,執行 table.draw(); 來載入資料

View完整的代碼↓

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>表單的Button click後才查資料(分頁、排序)</title>
    <!--引用dataTables css-->
    <link rel="stylesheet" href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" />
</head>
<body>

     <!--表單↓通常放置查詢條件-->
    <!--action在這裡可以不用給,因為發出ajax時就會指派url-->
    <form name="myForm" action="" method="post">
        <label>MyTitle:</label> <input type="text" name="MyTitle" /><br />
        <label>MyMoney:</label> <input type="text" name="MyMoney" /><br />
        <input type="button" id="btnQuery" value="查詢" />
    </form>



    <!--DataTables的表格-->
    <table id="myDataTalbe" class="display">
        <thead>
            <tr>
                <th>#</th>
                <th>MyTitle</th>
                <th>MyMoney</th>
            </tr>
        </thead>
    </table>



    <!--引用jQuery-->
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.js"></script>
    <!--引用dataTables.js-->
    <script type="text/javascript" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>

    <script type="text/javascript">
        $(function () {
             
             let table =
                 $("#myDataTalbe").DataTable({
                     /* processing: true,*/  //→這太醜了,所以關閉
                     searching: false,  //關閉filter功能
                     columns: [ //指定Server端的資料行繫結名稱
                         { data: "sysid" },
                         { data: "MyTitle" },
                         { data: "MyMoney" }
                     ], 
                     serverSide: true,//啟用ServerSide模式  
                     //ajax參數在1.9版以前參數名稱為sAjaxSource
                     ajax: {
                         method:"get",//或post,自由決定,影響到Server端用FormCollection取值或Request.QueryString取得DataTables傳遞的資訊
                         url: "@Url.Action("GetData_Full", "Home")", 
                         data: function (d)
                         {  
                             d.MyTitle = $("input[name=MyTitle]").val();
                             d.MyMoney = $("input[name=MyMoney]").val();
                         }
                     },
                     deferLoading: 0, //初始化DataTable時,不發出ajax
                     order: [[0, "asc"]], //預設排序資料行(由0算起)
                     orderMulti : false
                 });

            $("#btnQuery").click(function () {
                 //按下表單的查詢按鈕後才發出Ajax載入資料 
                 table.draw(); //或table.ajax.reload();

            });
        });
    </script>
</body>
</html>


而Server端要回傳的資料格式,大概長這樣↓

draw:為了避免XSS攻擊,DataTables傳遞什麼值,Server端就跟著回傳什麼值

recordsTotal:經過查詢後的資料總筆數

recordsFiltered:再度經過filter後的資料總筆數,由於本範例未使用filter功能,所以此數值和recordsTotal一樣

recordsTotal和recordsFiltered兩個數值影響到的是下面紅框的呈現,最好別亂給值

data:要回傳的資料,注意陣列中的每一筆物件,其屬性名稱要和DataTables()裡的columns名稱對得上

完整Controller代碼↓

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
//透過NuGet加入System.Linq.Dynamic,Server端Linq排序用得到
using System.Linq.Dynamic;
using System.Text;
using System.Threading;
using System.Web;
using System.Web.Mvc;

namespace WebDataTables.Controllers
{
     /// <summary>
     /// 每一筆資料Entity
     /// </summary>
    public class MyRecord
    {
        public int sysid { get; set; }
        public string MyTitle { get; set; }
        public int MyMoney { get; set; }

    }
    public class HomeController : Controller
    {
        /// <summary>
        /// DataSource 資料集合,通常為DB裡的資料
        /// </summary>
        private List<MyRecord> _myRecords = new List<MyRecord>();
        public HomeController()
        {//在建構子裡新增資料
            for (int i = 0; i < 300; i++)
            {
                this._myRecords.Add(new MyRecord()
                {
                    sysid = i + 1,
                    MyTitle = "MyTitle" + i,
                    MyMoney = i * 1000
                });
            }//end for 
        }
         
        
        /// <summary>
        /// 呈現畫面
        /// </summary> 
        public ActionResult Index()
        {
            return View();
        }
        
        /// <summary>
        /// 查詢資料
        /// </summary> 
        public ActionResult GetData_Full(int draw, int start, int length,//→此三個為DataTables自動傳遞參數
          //↓以下兩個為表單的查詢條件,請依自己工作需求調整  
          string MyTitle, int? MyMoney)
        {
            
            int skip = start;//起始資料列索引值(略過幾筆)
 
            #region jQuery DataTables的排序資料行
            //jQuery DataTable的Column index
            string col_index = Request.QueryString["order[0][column]"]; //←↓如果是接post method的話,記得改成Request.Form["order[0][column]"]
            //col_index 換算成 資料行名稱
            //排序資料行名稱
            string sortColName = string.IsNullOrEmpty(col_index)? "sysid" : Request.QueryString[$@"columns[{col_index}][data]"];
            //升冪或降冪
            string asc_desc = string.IsNullOrEmpty(Request.QueryString["order[0][dir]"]) ? "desc" : Request.QueryString["order[0][dir]"];//防呆
            #endregion

            //查詢&排序後的總筆數
            int recordsTotal = 0;
            //要回傳的分頁資料
            List<MyRecord> pagedData = new List<MyRecord>();

            //總資料
            var query = this._myRecords.AsEnumerable();  
            //查詢
            if (!string.IsNullOrEmpty(MyTitle))
            {
                query =  this._myRecords.Where(m=>m.MyTitle.Contains(MyTitle));
            }
            if (MyMoney.HasValue)
            {
                query = this._myRecords.Where(m=>m.MyMoney==MyMoney);
            }
             
            //排序
            query = query.OrderBy($@"{sortColName} {asc_desc}"); //排序使用到System.Linq.Dynamic

            recordsTotal = query.Count();//查詢後的總筆數
            
            if (length==-1)
            {//抓全部資料
               pagedData = query.ToList(); 
            }
            else
            {//分頁 
               pagedData = query.Skip(skip).Take(length)
                           .ToList();
            }
            

            //回傳Json資料
            var returnObj =
              new
              {
                  draw = draw,
                  recordsTotal = recordsTotal,
                  recordsFiltered = recordsTotal,
                  data = pagedData//分頁後的資料 
              };
            return Json(returnObj, JsonRequestBehavior.AllowGet);
        }

         
          
    }
}

執行結果:

結語

想更進一步熟悉DataTables還有什麼其它寫法的人,可以再參考其他網友文章

Datatables 在asp.net mvc中的使用 - 醉丶千秋 - 博客园

jQuery DataTable 後端分頁

Using jQuery DataTables with Server-Side Processing with ASP.NET MVC - CodeProject

Using DataTables Grid With ASP.NET MVC

Paginating data with Jquery Datatables and ASP.NET MVC