Thursday, June 14, 2012

在ASP.NET MVC 4中使用Kendo UI Grid


之前寫過用ASP.NET WebForm作為AJAX式資料源的Kendo UI Grid範例,最近計劃在一個小專案試用ASP.NET MVC 4 RC,面對的第一個需求又是"以清單呈現查詢結果",就來看看如何用ASP.NET MVC 4 RC滿足Kendo UI Grid的需求吧!
記得前一次用ashx寫資料端,花了不少功夫處理分頁及排序,而且還沒實做Filter過濾功能。但在ASP.NET MVC上,要整合Kendo UI Grid,則有很酷的方便選擇 -- KendoGridBinder!!
以下是我實做Kendo UI Grid + ASP.NET MVC 4的過程:
  1. 建立一個ASP.NET MVC 4專案 
  2. 使用NuGet安裝KendoUIWeb及KendoGridBinder
  3. 借用上回的SimMemberInfo Model類別 ,放在Model目錄下: 
    排版顯示純文字
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using System.Reflection;
    using System.Web;
     
    namespace KendoGridMvc.Models
    {
        //模擬資料物件
        public class SimMemberInfo
        {
            public string UserNo; //會員編號
            public string UserName; //會員名稱
            public DateTime RegDate; //註冊日期
            public int Points; //累積點數
     
            //模疑資料來源
            public static List<SimMemberInfo> SimuDataStore = null;
     
            static SimMemberInfo()
            {
                Random rnd = new Random();
                //借用具名顏色名稱來產生隨機資料
                string[] colorNames = typeof(Color)
                    .GetProperties(BindingFlags.Static | BindingFlags.Public)
                    .Select(o => o.Name).ToArray();
                SimuDataStore =
                    colorNames
                    .Select(cn => new SimMemberInfo()
                    {
                        UserNo = string.Format("C{0:00000}", rnd.Next(99999)),
                        UserName = cn,
                        RegDate = DateTime.Today.AddDays(-rnd.Next(1000)),
                        Points = rnd.Next(9999)
                    }).ToList();
            }
        }
    }
  4. 要引用Kendo UI,需要載入必要的JS及CSS,此時昨天介紹過的ASP.NET MVC打包壓縮功能馬上派上用場! 編輯App_Start/BundleConfig.cs,加入以下程式: 

    排版顯示純文字
                bundles.Add(new ScriptBundle("~/bundles/kendoUI").Include(
                    "~/Scripts/kendo/2012.1.322/kendo.web.min.js"
                    ));
    //經實測,SytleBundle virtualPath參數使用"2012.1.322"會有問題,故向上搬移一層
    //將/Content/kendo/2012.1.322的內容搬至Content/kendo下
                bundles.Add(new StyleBundle("~/Content/kendo/css").Include(
                    "~/Content/kendo/kendo.common.min.css",
                    "~/Content/kendo/kendo.blueopal.min.css"
                    ));
    PS: 此處有一個眉角:由於CSS檔路徑會被當成引用圖檔的基準,原本Kendo UI的.css及圖檔被放在~/Content/kendo/2012.1.322/下,理論上StyleBundle應設成"~/Content/kendo/2012.1.322/css”,才能引導瀏覽器到該目錄下取用圖檔。不幸地,我發現StyleBundle的virtualPath參數出現2012.1.322時,會導致Styles.Render("~/Content/kendo/2012.1.322/css”)時傳回HTTP 404錯誤~ 為克服問題,我決定將2012.1.322目錄的內容向上搬一層,直接放在~/Content/keno目錄下,並將virtualPath設成"~/Content/kendo/css",這樣就能避開問題。
  5. 為了省去每個View都要加掛Kendo UI JS及CSS的麻煩,我索性將它們加在~/Views/Shared/_Layout.cshtml中: 
    排版顯示純文字
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width" />
        <title>@ViewBag.Title</title>
        @Styles.Render("~/Content/themes/base/css", "~/Content/css",
                       "~/Content/kendo/css")
        @Scripts.Render("~/bundles/modernizr")
        @Scripts.Render("~/bundles/jquery", "~/bundles/kendoUI")
        @RenderSection("scripts", required: false)
    </head>
    <body>
        @RenderBody()
    </body>
    </html>
  6. 網頁Index.cshtml的Client端做法,則跟上回WebForm AJAX範例幾乎完全相同:
    排版顯示純文字
    @section Scripts
    {
        <style>
            body { font-size: 9pt; }
            #dvGrid { width: 500px; }
            span.hi-lite { color: red; }
            #dvGrid th.k-header { text-align: center; }
        </style>
        <script>
            $(function () {
                //建立資料來源物件
                var dataSrc = new kendo.data.DataSource({
                    transport: {
                        read: {
                            //以下其實就是$.ajax的參數
                            type: "POST",
                            url: "/Home/Grid",
                            dataType: "json",
                            data: {
                                //額外傳至後方的參數
                                keywd: function () {
                                    return $("#tKeyword").val();
                                }
                            }
                        }
                    },
                    schema: {
                        //取出資料陣列
                        data: function (d) { return d.data; },
                        //取出資料總筆數(計算頁數用)
                        total: function (d) { return d.total; }
                    },
                    pageSize: 10,
                    serverPaging: true,
                    serverSorting: true
                });
                //JSON日期轉換
                var dateRegExp = /^\/Date\((.*?)\)\/$/;
                window.toDate = function (value) {
                    var date = dateRegExp.exec(value);
                    return new Date(parseInt(date[1]));
                }
                $("#dvGrid").kendoGrid({
                    dataSource: dataSrc,
                    columns: [
                        { field: "UserNo", title: "會員編號" },
                        { field: "UserName", title: "會員名稱",
                            template: '#= "<span class=\\"u-name\\">" + UserName + "</span>" #'
                        },
                        { field: "RegDate", title: "加入日期",
                            template: '#= kendo.toString(toDate(RegDate), "yyyy/MM/dd")#'
                        },
                        { field: "Points", title: "累積點數" },
                    ],
                    sortable: true,
                    pageable: true,
                    dataBound: function () {
                        //AJAX資料Bind完成後觸發
                        var kw = $("#tKeyword").val();
                        //若有設關鍵字,做Highlight處理
                        if (kw.length > 0) {
                            var re = new RegExp(kw, "g");
                            $(".u-name").each(function () {
                                var $td = $(this);
                                $td.html($td.text()
                               .replace(re, "<span class='hi-lite'>$&</span>"));
                            });
                        }
                    }
                });
                //按下查詢鈕
                $("#bQuery").click(function () {
                    //要求資料來源重新讀取(並指定切至第一頁)
                    dataSrc.read({ page: 1, skip: 0 });
                    //Grid重新顯示資料
                    $("#dvGrid").data("kendoGrid").refresh();
                });
            });
        </script>
    }
    <div style="padding: 10px;">
        關鍵字:
        <input id="tKeyword" /><input type="button" value="查詢" id="bQuery" />
    </div>
    <div id="dvGrid">
    </div>
  7. 最後來到重頭戲,負責以AJAX方式傳回資料的HomeController.cs的Grid() Action:
    排版顯示純文字
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using KendoGridBinder;
    using KendoGridMvc.Models;
     
    namespace KendoGridMvc.Controllers
    {
        public class HomeController : Controller
        {
            //
            // GET: /Home/
     
            public ActionResult Index()
            {
                return View();
            }
     
            public JsonResult Grid(KendoGridRequest request, string keywd)
            {
                var result = SimMemberInfo.SimuDataStore.Where(o =>
                string.IsNullOrEmpty(keywd) || o.UserName.Contains(keywd));
                return Json(new KendoGrid<SimMemberInfo>(request, result));
            }
        }
    }
    什麼? 沒看錯吧? 這樣就好? 
    是的,感謝Ryan Whitmire及Jose Ball的佛心與巧思,只要return Json(new KendoGrid<T>(KendoGridRequest, IEnumerable<T>)),餘下的換頁、排序,甚至欄位過濾功能,就都交給KendoGridBinder全權處理囉!
實際測試,換頁、排序功能一切正常,有了Kendo UI助陣,ASP.NET MVC之路順利多了~

No comments: