事情永遠是自己想太多。 -- By KKBruce我在三月時寫了 ASP.NET MVC 3概觀正體中文版及五月時寫了 ASP.NET MVC 3 RTM Tools Update for CHT,算是把整個 ASP.NET MVC 3 所有更新的內容整個灠覽一篇,但其中一直有一點一直讓我心癢癢的,就是在ASP.NET MVC 3概觀正體中文版裡關於 Razor 提供新的 HTML Helper (Chart, WebGrid, Crypto, WebImage, WebMail),為什麼好東西不和好朋友分享呢?因為相關文章都把這些 ASP.NET MVC 3 新 Helper 和 Razor 放在一起使用,造成我的誤解,以為要使用這些新 Helper 必須要和 Razor 一起才能使用,後來發現我錯了。
在查詢 ASP.NET MVC 3參考 (英文) (ASP.NET MVC 3 Reference) 時,發現了一些好玩的事,怎麼我找不到任何關於 ASP.NET MVC 3 新增的 HTML Helper 的相關類別,ASP.NET MVC 3 只新增了一個 System.Web.Mvc.Razor 類別,其他四個與ASP.NET MVC 2 一樣,經查詢,這些 ASP.NET MVC 3 的新 HTML Helper 是在 System.Web.Helper 類別,而且此類別是位於 Extensions 之下,而非 Frameowrk,如果你專案是開 ASP.NET MVC 3 專案,預設是會加入參考。
都已經預設加入參考,那我可不可以在非 Razor 下使用它呢?答案當然是 No problem。
MVC 3 New HTML Helper 1 -- WebGrid
在MVC 3之前(不就 MVC 2呢?),如果我們要對一個表格(或說 Model)要做一些進階的效果,例如,我們想做分頁,或除了分頁還是做分頁的Ajax效果…?網路上都有範例可以參考,例如使用jqGrid,或使用Martijn Boland實作的 MvcPaging函式庫,或大陸有位杨涛所實作 MvcPager分页控件等,都是很棒的解決方案。現在我們能有還能使用 ASP.NET MVC 3 所提供的 WebGrid。我們使用預設的 HomeController 的 Index Action來示範,我們先清除Index Action的程式碼及刪除Index.aspx這個View。
Function Index() As ActionResult Dim nw As New NorthwindEntities Dim customers = nw.Customers().ToList() Return View(customers) End Function
Index()動作很簡單,使用Entiry Framework來傳入Customer資料表。
然後我們使用強型別(Customers)來建置 Index.aspx 這個View,請直接拉到最後面,
<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of IEnumerable (Of MvcWebGrid.Customers))" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Index </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>Index</h2> <p> <%: Html.ActionLink("Create New", "Create") %> </p> <table> <tr> <th> CompanyName </th> <th> ContactName </th> <th> ContactTitle </th> <th> Address </th> <th> City </th> <th> Region </th> <th> PostalCode </th> <th> Country </th> <th> Phone </th> <th> Fax </th> <th></th> </tr> <% For Each item In Model %> <% Dim currentItem = item %> <tr> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.CompanyName) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.ContactName) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.ContactTitle) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.Address) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.City) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.Region) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.PostalCode) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.Country) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.Phone) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.Fax) %> </td> <td> <%: Html.ActionLink("Edit", "Edit", New With {.id = currentItem.CustomerID}) %> | <%: Html.ActionLink("Details", "Details", New With {.id = currentItem.CustomerID}) %> | <%: Html.ActionLink("Delete", "Delete", New With {.id = currentItem.CustomerID}) %> </td> </tr> <% Next %> </table> <h2>WebGrid (有分頁及排序)</h2> <p> <%: New WebGrid(Model).GetHtml()%> </p> </asp:Content>
我們使用List Scaffold建置出Index.aspx這個View,在最後面新增一行「<%: New WebGrid(Model).GetHtml()%>」,讓List Scaffold 所產生效果與 WebGrid 產生的效果比較一下,
上半部分,原始的 List 是使用 For Each來一筆一筆跑出資料,利用 79行程式碼跑出一個超長的Table。下半部分,WebGrid 產生的 Table ,預設有含有「排序及分頁」兩大功能,而且全部只有一行程式加一個參數,有沒有感動到。
WebGrid 能做的不只有這樣,我們針對傳入的Model及Html控制到很細項目,例如,針對Model的控制,
例如,
<%: New WebGrid(Model, defaultSort:="ContactName", rowsPerPage:=5).GetHtml()%>
預設我想針對「ContactName」來排序,且每一分頁使用 5 筆資料。另外,由於預設參數很多,建議可以使用「指名參數(:=, 冒號加等號)」的寫法來設定。能不能有分頁,使用「CanPage」,能不能排序,使用「canSort」,實在很方便。
再來,我們再針對HTML (table) 來進行設置,
這邊我們改寫一下Code,先產生WebGrid的執行個體,這樣才有辨法傳入GetHtml()方法當參數:
<% Dim wg = New WebGrid(Model, defaultSort:="CustomerID", rowsPerPage:=5)%> <%: wg.GetHtml(columns:=wg.Columns( wg.Column("Address", "地址"), wg.Column("City", "城市"), wg.Column("CompanyName", "公司")))%>
我們看一下結果,
我們希望產生三個Column,而且對應欄位顯示中文。那麼我們可不可以產生CURD呢?還是那一句話「No problem!」,不然就像桌子少一支腳,怎麼行。
<% Dim wg = New WebGrid(source:=Model, defaultSort:="CustomerID", rowsPerPage:=5)%> <%: wg.GetHtml(columns:=wg.Columns( wg.Column(format:=Function(item) Html.ActionLink("編輯", "Edit", New With {.id = item.CustomerID})), wg.Column(format:=Function(item) Html.ActionLink("刪除", "Delete", Nothing, New With {.onclick = String.Format("deleteRecord('CustomerID', '{0}')", item.CustomerID), .href = "JavaScript:void(0)"})), wg.Column("Address", "地址"), wg.Column("City", "城市"), wg.Column("CompanyName", "公司")) )%>
我們產生兩個新欄位,我們透過匿名函式來產生對應的Link,編輯及刪除所產生對應的HTML Code,
<!-- 編輯 --> <a href="/Home/Edit/ALFKI">編輯</a> <!-- 刪除 --> <a onclick="deleteRecord('CustomerID', 'ALFKI')" href="JavaScript:void(0)">刪除</a>
對照HTML Code來看,就比較能了解我們設定那些參數的用意。再來,我們在進行分頁時,可以看得出來是使用 Postback 的方法,如果要使用 Ajax 的方法,WebGrid也是支援的,
<div id="DivCustomer"> <% Dim wg = New WebGrid(source:=Model, defaultSort:="CustomerID", rowsPerPage:=5, ajaxUpdateContainerId:="DivCustomer")%> <%: wg.GetHtml(columns:=wg.Columns( wg.Column(format:=Function(item) Html.ActionLink("編輯", "Edit", New With {.id = item.CustomerID})), wg.Column(format:=Function(item) Html.ActionLink("刪除", "Delete", Nothing, New With {.onclick = String.Format("deleteRecord('CustomerID', '{0}')", item.CustomerID), .href = "JavaScript:void(0)"})), wg.Column("Address", "地址"), wg.Column("City", "城市"), wg.Column("CompanyName", "公司")) )%>
我們在第一行的程式碼內指定屬性「ajaxUpdateContainerId:="DivCustomer"」,意用是我們要使用Ajax,更新到「HTML元素id為『DivCustomer』內」,這裡有兩點需注意,
- 需把WebGrid包在指定的HTML元素內;
- 需引用jQuery;
最後我們來討論「分頁」效能問題,其實也不用討論,直接看它所傳入SQL Server的T-SQL:
SELECT [Extent1].[CustomerID] AS [CustomerID], [Extent1].[CompanyName] AS [CompanyName], [Extent1].[ContactName] AS [ContactName], [Extent1].[ContactTitle] AS [ContactTitle], [Extent1].[Address] AS [Address], [Extent1].[City] AS [City], [Extent1].[Region] AS [Region], [Extent1].[PostalCode] AS [PostalCode], [Extent1].[Country] AS [Country], [Extent1].[Phone] AS [Phone], [Extent1].[Fax] AS [Fax] FROM [dbo].[Customers] AS [Extent1]
這不是件好事,它不會先做分頁再回傳,而是每一次回傳所有資料再進行分頁及排序等動作。( 因為你原始的 Action 裡的資料,本來就沒進行分頁 )
這時候你有兩種解決方法,一是前面所提使用Martijn Boland實作的 MvcPaging函式庫,兩是參考這篇「Efficient Paging with WebGrid Web Helper - ASP.NET MVC 3」所提供的方法。
解決 WebGrid 分頁問題 -- 使用MvcPaging函式庫
- 引用MvcPaging專案,然後編譯;
- 在HomeController.vb裡 Imports MvcPaging
- 在HomeController.vb 加上page 參數,撰寫分頁程式,
- Index.aspx裡Inherits的Of IEnumerable改為Of MvcPaging.IPagedList
- 在Index.aspx撰寫分頁程式
HomeController.vb
Function Index(page As Integer?) As ActionResult Dim nw As New NorthwindEntities 'Dim customers = nw.Customers().ToList() Dim customers = nw.Customers().OrderBy(Function(c) c.CustomerID).ToPagedList(If(page >= 1, page - 1, 0), 5) Return View(customers) End Function
Index.aspx:在原WebGrid程式碼之下加上
<%= Html.Pager(Model.PageSize,Model.PageNumber ,Model.TotalItemCount) %>
我們利用MvcPaging來分頁,因為MvcPaging設定的分頁數量與我們設定WebGrid的「rowsPerPage:=5」一樣,所以我們只是單純的把WebGrid當成一個顯示用的物件,另外ajaxUpdateContainerId設定也可以移除了,因為我們不是使用 WebGrid 來進行分頁。
解決 WebGrid 分頁問題 -- 使用Malcolm Sheridan MVP提供的方案(修正運作無誤版)
原網頁上提供的 jQuery 無法正常運作,我修正了一下,在 Chrome 11.x, Firefox 4.x, IE 9 都能正常運作。- 在 HomeController.vb 匯入 System.Web.Helpers
- 新增兩個Action,一個用來檢示,一個用來處理分頁要求
- 撰寫 jQuery 分頁程式碼
- 在檢示View中,引用 jQuery 程式碼。
''' <summary> ''' 檢示結果 ''' </summary> ''' <returns></returns> ''' <remarks></remarks> Function WebGridPager() As ActionResult Return View() End Function ''' <summary> ''' 處理分頁 ''' </summary> ''' <param name="page">第幾頁</param> ''' <returns>JSON物件</returns> ''' <remarks></remarks> Function EfficientPaging(page As Integer?) As ActionResult ' 使用 Northwind 資料庫 Dim nw As New NorthwindEntities Dim skip As Integer = If(page.HasValue, page.Value - 1, 0) ' 以 Customers 資料表為例 Dim data = nw.Customers().OrderBy(Function(c) c.CustomerID).Skip(skip * 5).Take(5).ToList() ' 傳入資料 Dim grid As New WebGrid(data) ' 產生Table Tag Dim htmlstring = grid.GetHtml(tableStyle:="webGrid", headerStyle:="header", alternatingRowStyle:="alt", htmlAttributes:=New With {.id = "DataTable"}) ' 回傳JSON物件 ' 無安全性問題,可使用 HTTPGET Return Json(New With {.Data = htmlstring.ToHtmlString(), .Count = nw.Customers.Count() / 5}, JsonRequestBehavior.AllowGet) End Function
我們新增一個 Pager.js ,撰寫相關 jQuery Code (修正原程式碼分頁無法運作的問題)
$(function () { $.getJSON('/Home/EfficientPaging', null, function (html) { // 把JSON取得動態 WebGrid 的HTML加入body $('body').append(html.Data); // 建立 footer var footer = createFooter(html.Count); // 動態為分頁 link 加上 click 事件 $('#DataTable tfoot a').live('click', function (e) { e.preventDefault(); // 取得文字,當成 page 參數回傳 var data = { page: $(this).text() }; // 當有 click 時,重新取得分頁資料 $.getJSON('/Home/EfficientPaging', data, function (html) { // 先將舊Table移除 $('#DataTable').remove(); // 把JSON取得動態 WebGrid 的HTML加入body $('body').append(html.Data); // 預設Table沒有tfoot,先加上 $('#DataTable thead').after(''); // 加入link到tfoot $('#DataTable tfoot').append(footer); }); }); }); }); function createFooter(count) { // 預設Table沒有tfoot,先加上 $('#DataTable thead').after(''); // 產生link var footer = ""; for (var i = 1; i < (count + 1); i++) { footer = footer + '<a href="#">' + i + '</a> '; } // 加入link到tfoot $('#DataTable tfoot').append(footer); return footer; }
在 WebGridPager.aspx 的 View 中加入引用 Pager.js (需 jQuery,如果 MasterPage 已經引用,只需引用 Pager.js ) 即可。
<body> <script src="../../Scripts/jquery-1.6.1.min.js" type="text/javascript"></script> <script src="../../Scripts/Pager.js" type="text/javascript"></script> </body>
那上兩種方法都是使用到 LINQ 的 Skip() 與 Take() 來進行分頁,些種分頁方法能很有效率的只取回相關的記錄,你以 5 筆為一頁,它一次就只會拿回 5 筆,這讓我們可以考慮把 WebGrid 使用在大量資料處理上。
WebGrid 參數
1 | Source | WebGrid指定資料來源。 |
2 | columnNames | Source中欄位的集合。 |
3 | defaultSort | 預設序排欄位名稱 |
4 | rowsPerPage | 每頁幾筆資料 |
5 | canPage | 是否允許分頁,預設true |
6 | canSort | 是否允許排序,預設true |
7 | ajaxUpdateContainerId | 指定Ajax更新的HTML元素ID |
8 | fieldNamePrefix | 給WebGrid產生的參數欄位加前綴字串,例如,未加時"?sort=CustomerID&sortdir=ASC",加一個前綴"grid_"後,"?grid_sort=CustomerID&grid_sortdir=ASC” |
9 | pageFieldName | 自定義分頁QueryString參數名稱 |
10 | sortFieldName | 自定義排序QueryString參數名稱 |
11 | sortDirectionFieldName | 自定義排序QueryString參數名稱 |
您好, 請問一下, 這個WebGrid我用在MVC4會出現
回覆刪除CS0246: The type or namespace name 'WebGrid' could not be found
應該要怎麼解決呢?