ASP.NET MVC 4 與 jqGrid 入門實作

這是篇逐步教學,主角是 ASP.NET MVC 4 和 jqGrid,目標是用 jqGrid 呈現資料,並且能夠分頁、排序。

2013-07-23 更新:
  • 用 jQuery 2.0.3 和 jQuery UI 1.10.3 和 jqGrid 4.4.4 重新時做一遍,將 RegisterBundles 方法重新抓圖,以減少練習時出錯的機會。
  • 增加一些注意事項。
  • 增加範例程式下載連結:Mvc4JQGridDemo.zip
前言

Phillip Haack 在 2009 年已經寫過一篇 ASP.NET MVC 搭配使用 jQuery Grid 的教學文章:Using jQuery Grid With ASP.NET MVC。該文的範例程式碼後來由 Tim Davis 翻新成 Visual Studio 2010 + .NET 4 + jQuery 1.4.4 + jqGrid 3.8.2

Davis 提供的範例原始碼,用 Visual Studio 2010 編譯和執行都沒有問題,可是我的 Visual Studio 2012 Update 1 連開啟專案都開不了,出現奇妙的 unsupported 錯誤訊息(八成是需要手動修改專案的組態檔吧)。另一方面,那個範例是用 Web Form 來作為 MVC 的 View。

於是,我用 Visual Studio 2012 + ASP.NET MVC 4 實作一遍,並將過程記錄下來。主要還是想試試 jqGrid。

開發環境

此範例會使用到的工具和技術如下:
  • Visual Studio 2012 Ultimate with Update 1
  • ASP.NET MVC 4
  • ADO.NET Entity Framework (非必要,只要能提供前端資料就行)
  • SQL Server + Northwind 資料庫(非必要,只要能提供前端資料就行)
  • jQuery 1.8.3 2.0.3
  • jQuery UI 1.9.2 1.10.3
  • jQuery jqGrid 4.4.1 4.4.4

整個練習步驟稍嫌冗長,因為是從建立 ASP.NET MVC 4 專案開始、到建立 Entity Framework 資料模型、建立 Controller,然後才談到 jqGrid 的部分。

如果想先看結果,請點這裡:執行結果的畫面截圖。如果已經熟悉 ASP.NET MVC 和 Entity Framework,只想看 jqGrid 的部分,可直接跳到 Step 4 或 Step 5。

Step 1: 建立專案

開啟 Visual Studio 2012,建立新的 ASP.NET MVC 4 Web Application 專案:


範本選擇 Basic,View engine 用預設的 Razor:

專案建立完成後,看一下 Content 和 Scripts 這兩個子目錄裡面有哪些東西:


可以看到,Visual Studio 內附的 jQuery 版本是 1.7.1,jQuery UI 是 1.8.20。稍後會用 NuGet 安裝新版本的套件。

Step 2:建立 MVC 的 M

如果不想使用 Entity Data Model,可直接跳過此步驟。

簡單起見,這裡直接把 Entity Model 建立在 Models 子目錄下:
  1. 在 Solution Explorer 中,專案的 Models 資料夾上點右鍵,選 Add \ ADO.NET Entity Data Model。
  2. 接著輸入 model 名稱:NorthwindModel。
  3. 選擇 Generate from database,建立資料連線,連接至 Northwind 資料庫,並選擇將連線字串儲存至組態檔。
  4. 選擇資料物件時,只挑 Customers 資料表就好,其餘不用。
底下是其中幾個關鍵步驟的截圖:



EF Data Model 建立完成後,先 Build 一下專案,免得待會建立 Controller 時沒有 Model class 可以挑選。

Step 3:建立 MVC 的 C 和 V

至 Solution Explorer,專案底下的 Controllers 資料夾上點右鍵,Add \ Controller...。然後,看圖:


利用此方式建立好 HomeController 之後,Views 資料夾裡面也會產生一個對應的 Home 目錄,其中有幾個對應至各個 action 的顯示頁面。

此時可以先看一下 HomeController.cs,工具已經幫我們產生好基本的程式碼,包括:建立 DbContext(我們的 NorthwindEntities)以及對應至各個 action 的方法。比如說,Index 方法會傳回全部的客戶資料:

View 的部分不是本文重點,暫且略過。

按 F5 以除錯模式執行應用程式,結果如下圖:

嗯,很陽春。接著試試 jqGrid。

Step 4:加入必要的套件

至 Solution Explorer,在專案名稱或其下的 Reference 項目上點右鍵,選擇 Manague NuGet Packages...。接著在對話窗右上方搜尋方塊中輸入「jquery jqgrid」,搜尋結果如下圖:

把圖中框起來的四個套件都安裝了吧!原先已經存在的 jQuery 1.7.x 和 jQuery UI 1.8.20 都會被替換成最新版本,也就是目前的 jQuery 1.8.3 和 jQuery UI 1.9.2。

Content 目錄底下會多出一些東西:

Step 5:打包 JavaScript 和 CSS 

打包,就是  ASP.NET 的 bundling 功能。在此範例中亦非必要,只是順便加進來練習罷了。

修改 App_Start 目錄下的 BundleConfig.cs,把先前新加入的 JavaScript 檔案連同 CSS 都一併綑起來。這裡只用畫面截圖顯示有變動的程式碼(如下圖),詳情可參考亞斯狼的 ASP.Net MVC中的Bundle 或黑暗執行緒的 ASP.NET MVC 4 RC的JS/CSS打包壓縮功能


請注意,如果你照著上圖中方框標示的區域來輸入程式碼,在輸入檔案的路徑和名稱時請務必一邊檢查自己的專案裡面是否真有符合的路徑和檔案名稱。比如說,有的 jQuery 版本的檔名是 jquery.jqGrid.src.js,有的則是 jquery.jqGrid.js。這個部分沒弄好,到後面執行程式時,可是會完全看不到資料的。
此外,在設定欲打包的檔案時,*.min.js 的檔案會被自動略過,故請勿打包 *.min.js 的檔案。

在伺服器端設定好要打包的檔案之後,前端網頁也得加入相應的程式碼才行。我們可以在所有網頁共用的 Views\Shared\_Layout.cshtml 中加入引用 JavaScript 的指令,像這樣:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Styles.Render("~/Content/themes/base/css")    
    @Scripts.Render("~/bundles/modernizr")
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/jqueryui")
    @Scripts.Render("~/bundles/jqueryjqgrid")
</head>
<body>
    @RenderBody()

    @RenderSection("scripts", required: false)
</body>
</html>

實務上,並非每個網頁都會用到 jqGrid,像上面的範例那樣在共用的 _Layout.cshtml 中引用全部的 JavaScript 檔案,並不是很好的作法。看到 </body> 結束前有個 @RenderSection("scripts", required: false) 嗎?這行敘述的用意是產生一個名為 "scripts" 的內容區塊,第二個參數帶入 false 表示若指定的區塊不存在也沒關係。這就是說,我們也可以利用這個預先挖好的坑:"scripts" 區塊,在各自的 View 當中利用 @section 來定義該區塊的內容,以便各個頁面載入自己需要的 JavaScript。詳情參見這篇文章,或這篇

Step 6:增加動作方法

HomeController 類別中既有的動作方法都保留不動,再新增一個方法:

public ActionResult JQGrid()
{
    return View();
}

注意此方法並沒有傳回任何資料,就只是預設的 view 而已。這是因為,網頁所需之資料將由它自己稍後以 Ajax 呼叫的方式向伺服器端取得。

動作方法寫好之後,接著要產生對應的 View:在此方法上點右鍵,選 Add View,然後看圖操作:


這樣會在 Views\Home\ 底下建立一個 JQGrid.cshtml,裡面僅短短數行程式碼。把它改成這樣(請注意第一行引用的模型類別,其命名空間必須符合你自己的專案名稱的命名空間。否則會出現編譯錯誤):

@model IEnumerable<Mvc4jqGrid.Models.Customer>

@{
    ViewBag.Title = "JQGrid";
}

<script type="text/javascript">

    jQuery(document).ready(function () {
        var grid = jQuery('#grid');
        grid.jqGrid({
            url: '/Home/GetCustomers/',
            datatype: 'json',
            jsonReader: {
                repeatitems: false
            },
            mtype: 'GET',
            colModel: [
                { name: 'CustomerID', label: '編號', width: 40, align: 'left' },
                { name: 'CompanyName', label: '公司名稱', width: 40, align: 'left' },
                { name: 'ContactName', label: '聯絡人', width: 40, align: 'left' },
                { name: 'ContactTitle', label: '職稱', width: 40, align: 'left' },
                { name: 'Address', label: '地址', width: 40, align: 'left' },
                { name: 'City', label: '城市', width: 20, align: 'left' }
            ],
            pager: '#pager',
            width: 660,
            height: 'auto',
            rowNum: 10,
            rowList: [5, 10, 20, 50],
            sortname: 'CustomerID',
            sortorder: "desc",
            viewrecords: true,
            caption: 'My first grid'
        });
    });
</script>

<h2>JQGrid</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table id="grid">
</table>
<div id="pager">
</div>


簡單地說,此網頁載入時會對伺服器發出 Ajax 呼叫,目標 URI 是 Home/GetCustomers/。因此,稍後還要回到 HomeController.cs 當中把這個函式補上。

注意 jsonReader 那個小區塊。由於稍後實作的 GetCustomers 方法所傳回的 JSON 字串會採用「帶名稱的資料格式」(例如 "CustomerID":"A001"),所以必須指定 jsonReader 的 repeatitems 參數為 false,否則程式執行時就只會看到一個空 grid(有欄位標題,沒有資料)。
摘自 jqGrid 說明文件
The repeatitems element tells jqGrid that the information for the data in the row is repeatable - i.e. the elements have the same tag cell described in cell element. Setting this option to false instructs jqGrid to search elements in the json data by name. This is the name from colModel or the name described with the jsonmap option in colModel.

Step 7:實作 GetCustomers 方法

在 HomeController 類別中加入以下程式碼:

/// <summary>
/// This method will be called via Ajax in Views/JQGrid.cshtml 
/// </summary>
/// <returns></returns>
public JsonResult GetCustomers(string _search, int? page, int? rows, string sord)
{
    var customers = db.Customers.ToList();

    int pageSize = rows.HasValue? rows.Value : 10;
    int pageNum = page.HasValue? page.Value : 1;
    int totalRecords = customers.Count;
    int totalPages = (int)Math.Ceiling((float)totalRecords / (float)pageSize);

    var jsonData = new
    {
        total = totalPages,
        page = pageNum,
        records = totalRecords,
        rows = customers.Skip((pageNum-1)*pageSize).Take(pageSize)
    };

    return Json(jsonData, JsonRequestBehavior.AllowGet);
}

此方法的幾個傳入參數,都是前端網頁的 jqGrid 呼叫所傳遞過來的(其實還有別的參數,可用 Fiddler 觀察之,或查 jqGrid 文件),主要用來處理分頁邏輯。由於參數名稱與前端網頁 jqGrid 送出的 HTTP request 的參數名稱相同,ASP.NET MVC 會自動幫我們配對並設定正確的參數值,所以這些參數的名稱不要亂改。但如果你先不想處理分頁,把這些參數全部刪除也是 OK 的。

其中的 jsonData 匿名型別包含四個屬性:total(總頁數)、page(目前要顯示第幾頁)、records(資料筆數)、rows(每頁顯示幾筆)。先知道這樣就好,更完整的說明請參考 jqGrid 官方文件

Step 8:Run and Pray

按 F5,亦即以除錯模式執行應用程式,然後把瀏覽器的網址列後面加上「Home/JQGrid」,然後雙手合十,看看結果是否如下圖:


如果 HomeController 的 GetCustomers 方法有提供正確格式的 JSON 資料,而且處理分頁的邏輯沒有寫錯,網頁應該能順利呈現一個美觀的 jqGrid,下方的換頁按鈕也能夠正確換頁。右上角的黑色圓鈕可收合/展開這個 grid。

問題排除

若整個網頁空蕩蕩的,連欄位標題都沒有,請檢查 step 5 是否有漏掉什麼。你可以利用瀏覽器的檢視原始碼功能來確認網頁是否有加入所有必要的 JavaScript 和 CSS,尤其是 jquery-*.js、jquery-ui-*.js、以及 jquery.jqgrid.js。參考以下範例:

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>JQGrid</title>
    <link href="/Content/site.css" rel="stylesheet"/>
    <link href="/Content/jquery.jqGrid/ui.jqgrid.css" rel="stylesheet"/>

    <link href="/Content/themes/base/jquery.ui.core.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.resizable.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.selectable.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.accordion.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.autocomplete.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.button.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.dialog.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.slider.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.tabs.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.datepicker.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.progressbar.css" rel="stylesheet"/>
    <link href="/Content/themes/base/jquery.ui.theme.css" rel="stylesheet"/>

    <script src="/Scripts/modernizr-2.6.2.js"></script>

    <script src="/Scripts/jquery-2.0.3.js"></script>

    <script src="/Scripts/jquery-ui-1.10.3.js"></script>

    <script src="/Scripts/jquery.jqGrid.js"></script>
<script src="/Scripts/i18n/grid.locale-tw.js"></script>

</head>

有出現欄位標題,卻沒顯示任何資料,那有可能是 GetCustomers 方法所傳回的 JSON 資料不符合 jqGrid 的規定。請檢查 step 7 和 step 8,並且利用瀏覽器來查看伺服器端傳回的資料內容,例如在網址列輸入:
http://主機名稱/Home/GetCustomers?_search=&page=1&rows=10&sord=desc

小結

這個實作練習只用到了 jqGrid 的一點點基本功能:呈現多筆資料並提供分頁和排序。往後可以繼續補強,完成 CRUD 這四種基本操作。另外有人針對 jqGrid 寫了 HTML 輔助類別,讓我們用 C# 語法來產生前端網頁所需要的 jqGrid 指令碼,例如 jqSuite(jqGrid 官方網站提供的商業套件)、Lib.Web.MvcjqGrid HTML HelperMvcJqGird 等等。這類輔助工具,我覺得還是在熟悉 jqGrid 之後再來考慮是否採用,會比較好。

除了 jqGrid,還有幾款 grid 套件似乎也是不錯的選擇,例如:jQuery DataTablesKendo UI grid 和 SlickGrid。改天有空試試。

下載完整範例原始碼:Mvc4JQGridDemo.zip

續集:切換 jQuery UI 的佈景主題

延伸閱讀

17 則留言:

  1. 大大您好
    當兵中想說找些MVC範例來實作練習
    順道回想一些作法跟觀念

    我在實作此範例時發生個問題

    我看GetCustomers的部份資料格式是對的

    我輸入localhost/Home/JQgrid

    但他一直給我HTTP 404的回覆

    只有Create的頁面是正常 其它像Edit, Delete 都一樣的問題

    請問大大知道是哪邊出問題嗎

    回覆刪除
  2. 你的 HomeController 有提供對應的 JQgrid 動作方法嗎?資訊不太夠,不太好判斷。有個方法是從頭再實作一遍,每一個小步驟做好都先 run 一遍確認無誤再做下一步。

    回覆刪除
  3. 你好
    第7步驟的
    int pageNum = age.HasValue? page.Value : 1;
    應修改成
    int pageNum = page.HasValue? page.Value : 1;

    回覆刪除
  4. 啊! 多謝指正。已經改好囉!

    回覆刪除
  5. 您好,
    我做完後沒有資料顯示,
    只有
    JQGrid
    Create New
    這兩行

    網址輸入 http://主機名稱/Home/GetCustomers?_search=&page=1&rows=10&sord=desc
    是有資料的
    步驟七的程式碼也有加入HomeController 類別中

    請問是哪裡出了問題呢? 謝謝

    回覆刪除
  6. Hi 烏魚子,
    文中的「問題排除」小節有提到檢查網頁是否有正確引用必要的 .js 檔案,這部分有檢查過嗎?我也更新了本文,增加一些注意事項,以及完整範例原始碼下載的連結。

    回覆刪除
  7. 索尼搭內特2013/8/1 下午5:27

    Dear Huanlin 大大,

    很期待您的 jqGrid + CRUD 的文章, 不知您 什麼時候有空 實作呢?

    回覆刪除
  8. Hi 索尼.NET,
    總是有其他東西要弄,時間很難確定哩!

    回覆刪除
  9. 作者已經移除這則留言。

    回覆刪除
  10. Hi! 我用的抓圖工具是 Snagit,可作出鋸齒狀(撕紙)效果。您提到其他進階技巧的文章,我只能說盡量找時間寫喔! (最近實在挺忙,又有好多文章打算要寫卻還沒完成)

    回覆刪除
    回覆
    1. 版主你好
      謝了你快速回函,已實作出來結果展示了,但一直弄不清楚下列函式中之int?為何須加上"?"標記,有何意義嗎(試著去除卻無法編譯成功),是為相容32及64位元之故嗎
      public JsonResult GetCustomers(string _search, int? page, int? rows, string sord)

      刪除
  11. int? 等於 Nullable<int>。以 GetCustomer 方法為例,前端可以不指定 page 或 rows 參數。
    參考:http://msdn.microsoft.com/zh-tw/library/2cf62fcy.aspx

    回覆刪除
  12. 請問一下,我是新手,按照您以上的步驟,發現了以下的問題, 會是那裡有問題呀? Thanks!
    未處理的例外狀況 位於行 17,欄 5 在 http://localhost:51421/Home/JQGrid 中
    0x800a138f - JavaScript 執行階段錯誤: 屬性 'jQuery' 的值為 null 或未經定義,且不是 Function 物件

    回覆刪除
  13. Hi Sandy,
    我猜可能是 Step 5 的部分出了差錯,導致 jQuery 函式庫沒有載入網頁。你可以再檢查一夏 Step 5 有沒有漏掉甚麼,或者在網頁載入之後,用瀏覽器的偵錯功能查看 jQuery 函式庫是否有載入頁面。例如在 Chrome 瀏覽器中頁面載入之後按 F12,然後查看已經載入的 JavaScript 清單,參考畫面截圖: http://tinyurl.com/kzhdzyl

    回覆刪除
  14. 你好! 我已經實做出來了,但發現無法排序 public JsonResult GetCustomers(string _search, int? page, int? rows, string sord)
    後來發現 sord 參數似乎沒有用到 是否有遺漏有關排序部分的程式碼 謝謝!!

    回覆刪除
  15. Hi Tommy,
    不好意思,這個範例應該是原本就沒有要示範排序的寫法,所以沒有用到 sord 參數喔。

    回覆刪除
  16. 你好! 感謝解答 後來我查過後 public JsonResult GetCustomers(string _search, int? page, int? rows, string sord, string sidx)
    多兩個參數 並在底下多寫一行 customers = customers.OrderBy(sidx + " " + sord);
    並在Nuget中載入 System.Linq.Dynamic 並using System.Linq.Dynamic; 後 便可以實現動態排序功能!!

    回覆刪除

技術提供:Blogger.