ASP.NET MVC + ADO.NET EF 項目實戰(三):引入jQuery

   jQuery是一個重要的客戶端框架,ASP.NET MVC默認的項目模板中就帶了這架。掌握這個框架對於更好地編寫ASP.NET MVC應用是非常重要的。事實上,網上有很多文章講述如何在ASP.NET MVC項目中使用jQuery。例如以下文章就是講關於jqGrid的:
Using jQuery Grid With ASP.NET MVC
Using jqGrid with ASP.NET MVC
另外,在CodePlex上就有很多項目幫助你更方便地應用jQuery,例如:
jQuery UI Extensions for ASP.NET MVC
jQuery Grid for ASP.NET MVC
 
但是,要更加方便地使用jQuery,僅僅知道以上還是不夠的。
一:瞭解ASP.NET MVC的侷限性
ASP.NET MVC有幾處是我不太喜歡的,但是似乎也沒有太好的解決方案。第一件事情是冗餘的Action。在一個Controller中每個Action的地位是平等的。但是很遺憾,一個帶Form的View總是需要兩個Action,一個用來GET;另一個用來POST。而我覺得這兩個Action是不平等的。用於Post的Action是依賴用於Get的Action的。我告訴你,這兩個Action其實是可以壓縮成一個的。另一個完全可以放到System.Web.Mvc中,大家公用,通過傳遞一個或多個delegate來處理。也就是說,形式上是一個,實際上仍然是兩個。說這個問題的意圖是想說明,我們可以把一個Ajax服務通過傳遞一個delegate來實現單入口。
第二件事情是Script的Render。我希望任何TagBuilder都可以引用自己的Script,在Html的Head段最後寫入。可惜WebFormViewEngine的Render過程中按次序進行的。這意味着,直到整個頁面寫入Response你都沒有機會去更正對Script的引用。其實我以前寫過關於ASP.NET MVC中Script的管理,不過實話說非常醜陋。其實我也可以告訴你,這個是可以實現的。在View中可以定義一個特殊的標籤,當遇到這個標籤的時候,將已經生成的Html寫入緩存,清空HtmlWriter,再Render剩餘的部分;最後再Render Script部分,最後交這三者合併,最後寫入Response。說這個問題的意圖是將客戶端複雜化以後給應用帶來的困難暴露出來。
任何一件事情,如果單獨在服務端完成,這個比較好處理。如果單獨在客戶端完成,這個也好處理。如果需要客戶端和服務端配合,就會弄出一些麻煩來。如果你要在一個頁面中顯示一個jqGrid,你一共至少需要做四件事情:
1.在你的View中加入相關的引用,包括JavaScript和CSS;包括jQuery、jQuery UI、jqGrid;包括相關的主題。
2.在你的View中適當的地方添加一個table標籤,並加上id,用於表格的容器;再添加一個div標籤,也加上id,用於分頁器的容器。
3.加入相關的腳本,將網格與給定的標籤進行綁定。其中需要定義網格的列信息。
4.加入一個數據獲取的Action,或者XML格式數據或者JSON格式數據。
對於第1件事情,似乎不算麻煩。如果不是過於挑剔的話甚至沒有什麼問題。
對於第2件事情和第3件事情,就會有衝突了。可以假設這兩件事情是兩個人做的,但是他們必須確保他們所使用的id是一樣的。一旦不一致,到底算誰的責任?對於第4件事情,就更麻煩了。因爲那已經是服務端的事情了。不僅獲取數據的Url要正確,連Column表和數據表也必須一致。一旦不一致,到底算誰的責任?
通常對於要求一致的事情,由一個人做比較好。所以,無比聰明的程序員會通過一個 HtmlHelper來根據單一的定義統一生成。這的確是一個好的思路。所以,使用jQuery是需要進行專門包裝的。
二:封裝jQuery及其插件
如果要一個基於jQuery的Total Solution,還真有比較大的困難。jQuery本身的功能是相當有限的,其豐富的功能要依賴一大堆的插件。幾乎每個插件要用在ASP.NET MVC中都需要包裝一下。好在jQuery插件有一些約定,所以封裝起來相對比較簡單。在封裝的時候有一點通常容易被忽略的是:作爲獨立於應用的封裝模塊中是可以帶自己的Controller的。舉個例子來說:如果你封裝了一個jQuery UI的主題管理器,主題管理的ModalDialog的Action,包括Get和Post你都是可以包裝在一個單獨的項目中的。如果你的控制器叫 jQueryController,兩個Action分別是ShowThemes和SetTheme,那你可以通過以下代碼來實現:
view sourceprint?var currTheme = ""; 
$(document).ready(function() { 
    var doClick = function() { 
        $("#themeGallery td img.currentTheme").removeClass(); 
        this.setAttribute("class", "currentTheme"); 
        currTheme = this.getAttribute("title"); 
    } 
    var doOK = function() { 
        $.post('/jQuery/SetTheme', { theme: currTheme }, function(data) { 
            $("#themeDialog").dialog("close"); 
            eval(data); 
        }); 
        return false; 
    } 
    var doCancel = function() { 
        $("#themeDialog").dialog("close"); 
    } 
    $("#themeDialog").load("/jQuery/ShowThemes"); 
    $("#ThemeButton").click(function() { 
        $("#themeGallery td img").click(doClick); 
        var dialogOpts = { 
            modal: true, 
            width: "662px", 
            height: "420px", 
            resizable: false, 
            buttons: { "OK": doOK, "Cancel": doCancel } 
        }; 
        $("#themeDialog").dialog(dialogOpts); 
    }); 
});
 
這樣,在任何需要設置主題的地方放一個<a>元素,id定義爲ThemeButton即可。基於同樣的原理,我們在封裝jqGrid的時候,可以使用如下定義的委託:

public delegate GridDataModel QueryableFunc(HttpContextBase context, int page, int rows, string sidx, string sord);這樣,單一的入口就是:
代碼
        public ActionResult Employees()        {            var model = new GridModel            {                Caption = "Employees",                Loader = (ctx, page, rows, sidx, sord) =>                {                    ...... 實現數據訪問                    return new GridDataModel                    {                        Total = q0.Count(),                        Page = page,                        Records = q.Count(),                        Rows = q.ToArray()                    };                }            };            model.Columns.Add(new ColumnModel            {                Name = "Id",                Width = "80px",                Align = ColumnAlign.Right,                Caption = "Id",                Index = "Id",                IsKey = true,                Sortable = true,                Hidden = true            });            ...... 加入其他列            return View(model);        }
 
這樣,在View中使用的擴展,生成的JavaScript代碼中,Url是由應用無關的模塊提供的,以View名稱+View中Table的id爲標識,存貯在服務端。獲取數據時提交該標識,找出數據加載器,從而實現異步數據加載。
jQuery的插件,除了jqGrid是必須封裝的以外,jQuery UI也是必須要封裝的。雖然很多人包括我在內,對jQuery UI有諸多不滿,主要的不滿都是基於功能,項目進度嚴重拖後於Roadmap上的承諾。另外,其代碼質量也不如jQuery。不過,畢竟jQuery UI是jQuery官方的產品,受到很多插件依賴,所以,暫時沒有可替代的。
 
此外,還有以下插件需要封裝:
帶CheckBox的TreeView;
導航Menu和上下文菜單;
封裝Google Maps API的gMap;
tinyMCE的jQuery插件;
用於佈局的Panel插件;
動態Form插件。
三:如何避免拼湊腳本
在ASP.NET MVC中需要經常拼湊腳本,這一點也非常令人討厭。討厭的並不是“拼湊腳本”,而是“經常”。需要“經常”拼湊腳本的原因是服務端的內容是動態輸出到客戶端的。如果通過某種固定的機制,智能地將服務端的單一代碼自動生成爲客戶端內容那將是非常令人高興的事情。換句話說,你在服務端直接寫C#代碼,然後有一個專門的工具將這些代碼翻譯成客戶端代碼。
我研究過JavaScript#,發現這條路完全行不通。首先,要麼翻譯源碼、要麼翻譯編譯後的代碼,但必須是整個文件地翻譯,而不能是某個代碼段。其次,JavaScript#必須依賴一整套程序集,而這些程序集是特有的。
我建立了一個新的項目,叫jQuery#,基於一套專門的庫,然後在運行時以較小的代價來實現反編譯,並翻譯成JavaScript代碼。如果僅僅只是生成jQuery所需要的客戶端代碼是非常簡單的,jQuery API畢竟比較少,每個插件的API也都相當有限。麻煩的是需要插入大量的客戶端Event Handler。這纔是難點所在。按我的計劃,今年6月初會發布jQuery#的第一個版本。 目標是可以這樣寫View:
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章