背景
我部正在開發的一個新項目,選擇了jQuery作為基礎的Javascript函數庫。同時確定了jQuery提供的ajax系列方法作為非同步推拉資料的基礎介面。
使用過ajax方法的同事應該是知道的,ajax方法需要提供一組選型,比如content-type、mime-type、data、async等等,這些選項的組合搭配可以構成不同的請求方式,必須理解HTTP的部分協議才能夠正確的完成配置。當然,也可以從google上去找配置好的程式碼片段copy過來用, 關於這種方式的缺陷就不多說了。
我的失誤
1. 沒有在代碼編寫之前,規定好如何利用jQuery的ajax方法。導致開發人員直接把ajax方法的調用寫在頁面的指令碼塊內,導致頁面內出現了如下所示的代碼:
$.ajax({ type: "post", contentType: "application/json",datatype: "json", async: false, url: "DemoHandler.asmx/GetData",data: "{index:1}", complete: function(e, s) { alert("ok")});
分析現象
上面的代碼的意圖是通過http的post方法,調用服務端web services的GetData方法,傳入的參數名稱為index,值為1 。這是以寫入程式碼方式與服務端達成的強耦合關係的編碼方式,隱含了如下的問題:
1. DemoHandler.asmx檔案的路經改變了,就會影響頁面內所有這樣的代碼;
2. 服務端GetData的方法簽名如果變化了,就會影響頁面內所有這樣的代碼;
3. 如果以後出現了更好的ajax架構,替換的工作量也是如同推倒重來的;
4. GetData這樣的方法如果被其他地方調用,採用的方法是copy一段這樣的代碼;
5. 當我們提倡前後台分開的開發的情況下,理論上前台開發人員精通的技術應該是div+css、js這些,後台開發才關注web services這樣的技術。只有這樣才能把各自的領域做精、最好。上面所示代碼的第一個模板是我提供的,因為我個人扮演了聯通前台和後台技術的橋樑角色,但反思起來他是個人行為,卻不是團隊力量的體現。
6. 開發人員可以自問一下,contentType: "application/json" 這個選項起到什麼作用?為什麼DemoHandler.asmx/GetData這樣的寫法可以成功調入到web services方法內部?為什麼data要寫成 "{index:1}" 這樣的樣式?我嘗試問了項目組的人,答案都是不知道。很明顯,真正和開發有關的只是業務,而不是這些底層協議方面的知識,它是可以對大家屏蔽的。
解決辦法
當把存在的問題分析清楚了,答案也就有了。我們為每一個web serivices寫一個專門的js檔案,它內部封裝了一組與web method一致簽名的函數。這樣頁面只通過js方法傳參數調用即可,而不再關注那些ajax調用選項的細節。達到了相關概念的邏輯統一封裝和管理、一點維護多點使用、不同職責的開發人員關注不同功能這些要求。
在這件事情的執行上,又出現了一個小插曲。負責寫js檔案的同學參考了其它網上牛人的js書寫風格,也玩起了風靡一時的js花招—動態對象、閉包。當然很快就被否定了,其它原因不多講,只有一點原因需要特別說明:任何編程技巧和技術的使用都要考慮它是否適合團隊中的一般水平,如果不適合就要考慮其它辦法。閉包顯然只是一個技巧而已,不是解決問題的王道。它的引入至少給一般人閱讀代碼增加了難度,所以要被PASS掉。
最終還是選擇採用大白話式的prototype來聲明服務物件的方法。一開始,我寫了一個頭部的初始化函數,該函數封裝好了所有的選項配置資訊,以及把服務物件擴充到jQuery體系中去,使開發人員可以按照一致的規格調用服務端函數。後期,其他開發人員只要在檔案的尾部按需追加一個個與服務端方法對應的函數即可。
最佳化與改進
為了利用.net script services對web services的擴充,項目組一開始規定後台直接提供web services對用戶端進行暴露。這對用戶端提交資料沒有問題,但對我們使用的某些控制項就不太適合了。因為控制項要求傳回值是一個純粹的json資料,而script services架構會在輸出的json資料外層再包上一個名d的根,這就導致控制項無法識別這個資料來源。於是項目組通過ashx檔案,實現Ihttphandler來手動處理請求。
很快,服務端工程裡多出了10幾個ashx檔案,因為用戶端需要10幾種不同的資料請求。每個ashx內部的ProcessRequest方法處理四件大事:1)設定WEB反饋的HTTP頭資訊;2)處理商務邏輯;3)序列化業務資料為JSON;4)把JSON資料寫到輸出資料流中。
這個現象說明了兩個問題:1)ashx檔案的數量會隨著應用需求的增多而不斷的增多,今後非常難以維護;2)四件事情只有一件是必須每次都做的,即處理商務邏輯,其它事件都是有規律並重複的。
基於對上述問題,項目組做出了改進,實現一個統一的WEB請求的調度和處理機制,使得同一類的請求可以放在一個ashx檔案內完成,並且每個請求直接映射到一個函數來完成,輸出、輸出、緩衝都由這個機制來處理;另外,普通開發人員只需要完成商務邏輯代碼即可,其它一概不要關心。
時間又過了2天,新的問題再此困擾了我。如果服務端增加一個新的方法,用戶端的js也要與之匹配的增加一個函數作為調用代理。這樣同一語意的函數,就產生了前後台兩點維護,這是不合理的。它也是一個機械性的工作,每次都要開發手動來做,這也是不人性的。於是,新的解決方案是當用戶端直接請求ashx檔案的時候,服務端自動產生相應的用戶端代理指令碼。這樣帶來了用戶端代碼更大的改進,我們只需要將 <script src=”..\xHandler.ashx”>置入頁面,一切就OK了。因為自動獲得的js指令碼會自動初始化用戶端服務,後續就可以在頁面的任何地方通過例如這樣的方法調用服務了: $.net. xHandler.GetData()
這樣改進之後,項目組的開發人員是充滿喜悅的,因為他們可以少做很多不必要的事情,可以把代碼寫的更加整潔,可以讓BUG變的更少。從此,我們也把整合jQuery.ajax和.net的技術方案固化了下來,以後團隊將依賴存在的技術穩定的工作,而不再依賴個人。順便說一句,在我們寫出這些代碼的時候,已經考慮過例如ajaxpro.net、.NET MVC這樣的架構,只是他並不適合我們而已。
最後,附源碼:WebHandler.zip
有朋友問:你為什麼不使用ajaxpro呢?我有一個很簡單的理由,因為我有足夠的實力來駕馭目前的需求。當我發現1000行代碼可以搞定需求的情況下,我是不會去引入一套DLL的,何況我也要學習它。完成此文章所提模組,筆者所花費的時間也僅僅是一個下午加一個晚上而已。我堅持一個理念:不要因為需要一點色彩,就把整個染缸都搬過來。
5.27 日有朋友問了一個問題:如果.ashx包含了n個方法,但前端只需要一個或者其中的幾個,那麼現在的情況用戶端都會產生包含所有方法的JS,請問如何解決這個問題?答案有三種:
1. 在指令碼引用塊script src 屬性引用.ashx時,後面可以帶上參數,如 src=xHandler.ashx?find=method&like=get*, 同步修改 GetSvr方法,使其簽名為 public StringBuilder GetSvr(string find,sting like) 這樣就可以獲得用戶端傳來的參數,其中find表示按什麼方式匹配方法,如按名稱匹配,like表示匹配的模式,如所有get開頭的方法,接下來修改該函數的內部邏輯,我就不細說了。
2. 在我的實際項目中迴避了這個問題,因為我採用的是整個項目只有一個Master頁面的形式,雖然開發過程中分開了不同的頁面,但實際運行中,這些頁面都作為web part的方式被非同步擷取html後,嵌入了Master的偽windows內(div實現)。所以,作為web part的頁面,實際上是不需要引用.ashx的,它直接共用了Master頁面已經載入的指令碼。
3. .ashx是可以直接通過瀏覽器載入產生指令檔的,我們可以人工下載這個檔案,裁剪出需要的方法,然後手工加入到項目中,並且不再需要在script處引用.ashx了。這也是為什麼每個.ashx產生指令檔都是模板化、單層次的原因,因為這樣就方便開發人員自己修改這個模板,而不依賴其它。這也是本項目的潛在的最佳實務之一。