本文章是由機器翻譯。

資料點

使用 jQuery DataTables 外掛程式交叉分析 OData

Julie Lerman

下載代碼示例

通過開放資料協定 (OData),資料生產者可以通過一種通用格式提供 Web 資料,只要使用支援 HTTP 的技術,任何人都可以使用這種格式。資料是通過 URI 提供的,您可以使用常見 HTTP 謂詞(GET、PUT、POST、MERGE 和 DELETE)進行資料交互。您可以直接通過 JavaScript 等語言或使用用戶端 API(如 Microsoft .NET Framework、Silverlight、PHP 或 Microsoft 提供的其他 API)直接處理資料交互。無論採用哪種方式,都可以按相同方式與所有 OData 源交互。

公開提供的 OData 服務(如 Netflix Inc.和 eBay Inc.提供的商用源、世界盃資料、甚至是提供 150 年棒球統計資訊的服務)正在不斷增加。

資料的訪問越來越容易,但是資料的提供呢?面對 150 年的棒球統計資訊或成千上萬部電影,用戶端仍需進行一些工作,才能檢索和導航所有這些資料。

在最近一次有關 jQuery 的 Vermont .NET 使用者組演示中,我從一個名為 DataTables 的 jQuery 外掛程式獲得靈感,該外掛程式投資成本低,可供使用者查詢大量資料。儘管 DataTables 可以在需要時與伺服器端代碼進行更多交互,其長處還是在於可以極快地進行用戶端處理。

JQuery 是一種用戶端 Web 技術(可以在任何類型的 Web 應用程式中使用),可以簡化 JavaScript 的處理。如果與 jQuery 領域的人士交流,總能感受到對方對這項技術的極大熱情。DataTables 是眾多 jQuery 外掛程式中的一種。在任何類型的 Web 應用程式中,都可以使用 jQuery。

我正好使用 .NET Framework 進行大部分工作,在本專欄中,我將同時使用 ASP.NET MVC 和 WebForms,演示如何在應用程式中使用一些基本的 DataTables 外掛程式功能。但是,WebForms 應用程式中的邏輯將由用戶端代碼驅動。我將使用 Netflix OData 服務 (http://odata.netflix.com/v1/Catalog),這樣,我可以演示如何處理在使用各種 OData 服務時可能遇到的一些常見缺陷。

可以從 datatables.net 下載 DataTables 外掛程式。如果您剛剛接觸 Odata 的用法,可以訪問 msdn.microsoft.com/data/odata 上 MSDN 開發中心的“WCF 資料服務”部分,從而加快熟悉速度。

使用 LINQ 和用戶端 API 查詢 OData

我將從一個簡單的 MVC 應用程式開始,在該應用程式中,我使用 Visual Studio 添加服務引用嚮導添加了對 http://odata.netflix.com/v1/Catalog 的服務引用。這進而創建了要在應用程式中使用的代理類,並基於該服務生成一個實體資料模型,如圖 1 所示。該嚮導還添加對 .NET Framework OData 用戶端庫 API 的引用。.NET Framework 和 Silverlight OData 用戶端庫都支援 LINQ 查詢,因此,OData 的使用相當簡單。

圖 1 解決方案資源管理器中的 MVC 專案

啟動控制器 HomeController.cs 使用 OData 用戶端庫和服務代理查詢以下特定流派的所有電影標題:Independent。查詢結果返回到與此特定控制器操作關聯的視圖:

public ActionResult Index() {
  var svcUri = new Uri("http://odata.
netflix.com//v1//Catalog");

  var context = new NetflixOData.NetflixCatalog(svcUri);
  var query = from genre in context.Genres
              where genre.Name == "Independent"
              from title in genre.Titles
              where title.ReleaseYear>=2007
              select title ;
  var titles = query.ToList();             
  return View(titles);
}

HomeController 索引視圖 (\Views\HomeController\index.aspx) 中的標記是執行所有相關表示邏輯的位置。 若要利用 jQuery 和 DataTables 外掛程式,需要向專案添加一組指令檔。 或者,可以指向連線腳本集(請參見asp.net/ajaxLibrary/CDN.ashx 處的 Microsoft AJAX 內容傳送網路),不過,我選擇在本地承載這些腳本。 DataTables 外掛程式的下載包含可以放入專案中的 \media 資料夾(包含這些腳本)。 您可以在圖 1 中看到我已完成此操作。

圖 2 包含 Index.aspx 檔的代碼清單。

圖 2 HomeController Index.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
 Inherits="System.Web.Mvc.ViewPage<IEnumerable<Title>>" %>
<%@ Import Namespace="JQueryMVC.Controllers" %>
<%@ Import Namespace="JQueryMVC.NetflixOData" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" 
  runat="server">
    Home Page
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" 
  runat="server">
  <head>
    <link href="../../media/css/demo-table.css" 
      rel="stylesheet" type="text/css" />
    <script src="../../media/js/jquery.js" 
      type="text/javascript"></script>
    <script src="../../media/js/jquery.dataTables.js" 
      type="text/javascript"></script>

    <script type="text/javascript" charset="utf-8">
        $(document).ready(function () {
           $('#Netflix').dataTable();
        });
    </script>
  </head>
  <div>
    <table id="Netflix">
      <thead><tr><th>Title</th>
                 <th>Rating</th>
                 <th>Runtime</th></tr></thead>
      <tbody>
        <% foreach (Title title in Model)
           { %>
             <tr><td><%= title.Name %> </td>
                 <td><%= title.AverageRating %></td>
                 <td><%= title.Runtime %></td></tr>
           <% } %>
      </tbody>
    </table>
  </div>
</asp:Content>

<head>節開頭處的 CSS 連結和兩個腳本源指向 CSS 格式設置以及關鍵的 jQuery 和 jQuery.datatables JavaScript 檔。

接下來,看一下頁面中呈現的表。DataTables 外掛程式依賴于 <thead>節中存儲的表 ID 和標題資訊。在此之後,一小段代碼迴圈訪問從 HomeController.cs 檔傳入視圖的 IEnumerable<Title>,在適當的列中顯示 Name、AverageRating 和 Runtime 值。

當頁面最初啟動時,標題標記中的 JavaScript 方法使用 jQuery 在表單中查找 Netflix 表,並將 dataTable 函數應用於該表。DataTables 是高度可配置的,但是對於這一調用 dataTable 函數的簡單表單,引用的表 Netflix 將獲取 DataTables 預設配置。圖 3 顯示生成的頁面。

圖 3 使用 DataTables 外掛程式顯示資料

DataTables 執行的操作不僅僅是使用 CSS 美化表。請注意,從底部的資訊可知,它檢索了 155 行。預設情況下,它開始按每頁 10 項進行用戶端分頁,不過,使用者可以在下拉清單中選擇每頁 25、50 或 100 項。搜索框基於表中所有可用列中的搜索結果進行篩選。使用者還可以按一下標題行列對資料進行排序。DataTables 外掛程式功能集十分豐富,甚至還有外掛程式的外掛程式。您可以在 datatables.net 網站上瞭解有關增強預設設置的更多資訊。

在用戶端查詢 OData

並不是所有情況都適合使用用戶端 API,因此,我將介紹更有挑戰性的任務,即在用戶端查詢 Netflix OData,而不借助其他可用庫(AJAX 用戶端庫)。我將使用 DataTables 外掛程式,同時避開 Netflix 服務施加的一些限制。在使用其他公共 OData 服務時,可能會遇到這些限制。

這次,我使用 ASP.NET WebForms 應用程式(儘管可以使用普通的舊 HTML,因為此頁面中未使用任何 .NET Framework 代碼)。此應用程式中還需要 \media 資料夾,但是不會創建針對服務的代理,因此無需使用“添加服務引用”。

dataTable 函數有一個名為 sAjaxSource 的方法,該方法自動從目標源檢索資料。不過,這要求以特定方式設置結果格式。OData 結果不符合這一要求。加利福尼亞的開發人員 Jeff Morris 撰寫了一篇很好的博客文章,演示如何在 WCF 資料服務查詢偵聽器中重塑 OData 結果。這篇文章位於 bit.ly/bMPzTH

我將使用 AJAX 以本機形式返回 OData,然後手動填充表。

頁面主體首先是定義表及其 <theader>(DataTables 需要它),以及一個空 <tbody>:

<body>
  <form id="form1" runat="server">
    <table id="Netflix" width="100%">
      <thead>
        <tr><th width="50%">Title</th>
            <th>Rating</th>
            <th>Runtime</th></tr>
      </thead>
      <tbody id="netflixBody"/>
    </table>
  </form>
</body>

頁面有一些函數:GetData、displayResults 和一個用於處理 Netflix 服務當前缺點之一的説明程式函數。與 OData 的 .NET 用戶端庫類似,有一個 AJAX 用戶端庫,它是 Microsoft ASP.NET AJAX API 的一部分。下麵是 AJAX 文檔中的一個示例,展示了一個使用此庫的 JavaScript OData 查詢的外觀:

function doQuery() {
var northwindService = new
Sys.Data.OpenDataServiceProxy("/Northwind.svc");
northwindService.query("/Customers", cbSuccess, cbFailure, userContext);

或者,您可以只使用 AJAX 和 jQuery,就像我在下麵的示例中那樣。 我們來看一看標頭腳本的開頭,其中包括 getData 函數:

<script type="text/javascript" charset="utf-8">
  var oTable;
  var query = "http://odata.
netflix.com/v1/Catalog/Titles?$orderby=Name&$top=500"

  $(document).ready(function () { getData() });

  function getData() {
    var url = query + "&$callback= displayResults" 
      + "&$format=json";
    $.ajax({ dataType: "jsonp", url: url });
  }

當頁面啟動時,document.ready 函數自動調用 getData。 getData 從預定義的 OData 查詢構造一個 URL,並追加參數以返回 OData 作為 JSON(預設 AtomPub 格式的一種替代形式),以及定義要在 AJAX 調用完成時執行的方法。

當 AJAX 調用完成時,使用 OData 查詢的結果調用 displayResults 函數(請參見圖 4)。

圖 4 準備 OData 結果以便顯示

function displayResults(results) {
  var entities;
  var redraw;

// Find data in results 
  if (results.d[0] == undefined) {
    queryNext = results.d.__next;
    entities = results.d.results;
  }
  else {
    queryNext = "";
    entities = results.d;
  }

  // Instantiate dataTable if necessary
  if (oTable ==null)
    oTable = $('#Netflix').dataTable();

  // Build table rows from data using dataTables.Add
  for (var post in entities) {
    if (post == queryResults.length-1)
      redraw = true; //only redraw table on last item
    else
      redraw = false;

    oTable.fnAddData([
      entities[post].Name, entities[post].Rating, 
      entities[post].Runtime],redraw);
  }

  // Continue retrieving results
  if (queryNext > "") {
    query = FixNetFlixUrl(queryNext);
    getData();
  }
}

使用“find data in results”注釋的代碼節處理我提到的 Netflix 局限之一。 Netflix 強制進行伺服器端分頁以保護其伺服器,每個請求僅返回 500 行。 您能想像有人會慢悠悠地查詢所有電影嗎? 我相信這種情況經常發生。 伺服器端分頁不會妨礙獲取更多行;您只需顯式執行此操作。

在用戶端處理大量資料正是 DataTables 所擅長的,您很可能需要利用該功能。 當檢索大量資料(例如 5,000 行)時,載入所有資料可能需要較長時間,但是一旦這些資料位於記憶體中之後,最終使用者就可以利用 DataTables 對這些資料執行所有類型的篩選和排序。

我第一次看到演示的 DataTables 時,演示人員說要將它用於企業報告工具,在該工具中會下載 80,000 行。 我反對這樣濫用 Internet 和伺服器。 不過,看到 DataTables 的運行性能後,我不再反對在受控方案中採用這種做法。

OData 提供了一種輕鬆請求另一批資料的方式,而 Netflix 提供此掛鉤供您使用。 下麵是一個請求 501 個結果的查詢:

http://odata.
netflix.com/v1/Catalog/Titles?$orderby=Name&$top=501

當查詢超過該服務的限制時,Netflix 使用 OData 繼續標記功能。 除了這些項之外,最後一項之後還包含一項查詢結果。 下麵是 AtomPub 格式的查詢:

<link rel="next"
  href="http://odata.
netflix.com:20000/v1/Catalog/Titles/?$orderby=
Name&$top=1&$skiptoken='1975%20Oklahoma%20National%20Championship%20
Game','BVZUb'" /> 
</feed>

skiptoken 參數告知查詢從何處開始下一組結果。在 JSON 中,結果開頭處名為 __next 的屬性中可以看到該項,如圖 5 所示。

圖 5 請求的 JSON 結果超過服務配置的返回資料量

當查詢未超過限制時,這些項直接位於 d 屬性內,如圖 6 所示。這便是為何 GetData 需要測試可在何處找到這些結果。如果存在繼續標記,則它將其存儲在 NextQuery 中,然後執行其餘查詢以便在記憶體中生成完整結果集。

圖 6 請求的 JSON 結果未超過服務配置的返回資料量

如果查看 __next 屬性,可以注意到,Netflix 向查詢添加了一個埠號 20,000。但是,如果直接執行該查詢,則會失敗。因此,在請求資料之前,應從 URI 中刪除該埠號。這就是我在請求資料之前調用的 FixNetFlixUrl 函數的用途。

在使用公共 OData 服務時,必須注意這類異常情況。現在,您已瞭解如何處理對返回結果數有限制的服務,以及如何處理在其繼續標記中形成重大更改的服務。

對於檢索的每個結果集,該方法都使用 DataTables fnAddData 方法向表添加每個項。重繪表的開銷很大,因此,我將 fnAddData 的 redraw 參數設置為 false,直至完成最後一項結果為止。重繪整個資料檢索會使 UI 更加流暢,而不是等到所有 5,000 行都已檢索並添加到表。

將初始查詢修改為返回 5,000 行,並將重繪推遲到最後執行之後,在 Internet 存取速度緩慢的佛蒙特州鄉村環境中,幾乎只需一分鐘便可捕獲所有行並顯示表。重繪每一行十分快捷,即使添加更多行,也可以與表交互。這真是一個驚喜。

所有 5,000 行都在表中之後,DataTables 可十分出色地進行排序和搜索。排序所需的時間不到一秒。搜索瞬間即可完成,它可回應搜索框中的每次擊鍵(請參見圖 7)。

圖 7 DataTables 中的即時搜索結果

針對 Internet Explorer 8 的小調整

DataTables 最近的一個更新會觸發一項 Internet Explorer 8 功能,在 DataTables 中處理大型結果集時,這非常不方便。當執行太多腳本行時,Internet Explorer 會顯示警告消息。

Microsoft 支援網站建議調整用戶端電腦的註冊表以更改此行為。要修復此應用程式,這不是合理的解決方案;如果可以避免,我不希望修改用戶端的註冊表設置。不過,還有另外一種方法。

DataTables 使用者論壇中的一篇文章建議修改 DataTables 指令檔。我實現了這種修改,效果很好。有關詳細資訊,請參見 bit.ly/co4AMD 上標題為“排序導致 IE 引發‘此頁面上的腳本造成 Internet Explorer 運行速度減慢’”的論壇主題。

眾多功能等待探索

希望您已瞭解足夠的資訊,可以理解我對這種強大外掛程式的興奮之情。在我演示的唯讀方案中,您可以執行更多操作來配置表的外觀及其行為。使用 DataTables 還便於編輯,如果要在伺服器端保留部分邏輯,也可以發揮 DataTables 的長處。

對我而言,通過使用 DataTables,最終使用者可以從不斷增多的公開 OData 服務中查詢大量可用資料,就像是極客天堂中進行的比賽。

Julie Lerman 是 Microsoft MVP、.NET 導師和顧問,住在佛蒙特州的山區。她在世界各地的使用者組和會議中演示過資料訪問和其他 Microsoft .NET Framework 主題。Lerman 是《Programming Entity Framework》(O'Reilly Media,2010)一書的作者,該書受到廣泛稱讚,她的博客位址是 thedatafarm.com/blog。請通過 Twitter.com 關注她:@julielerman.

衷心感謝以下技術專家對本文的審閱:Rey BangoAlex James