如果你既想學習ASP.NET AJAX,又想學習Windows Workflow Foundation,還想學習LINQ(包括DLinq和XLinq),能夠一次過滿足你三個願望的除了Kinder出奇蛋就是本期Random Clipping重點推薦的這篇文章了。
Build Google IG like Ajax Start Page in 7 days using ASP.NET Ajax and .NET 3.0
點選連結開啟文章後,相信大部分讀者的第一反應都是“哇!那麼長,怎麼可能看得完啊!”既然文章標題說是用7天時間來製作一個這樣的首頁,那麼你也可以先用del.icio.us把文章收藏下來然後分開幾天看。想學新東西當然就要花點時間的啦,當你耐心把Omar Al Zabir這7天的開發記錄讀完,再結合原始碼深入理解一下它的設計,相信你就能對它所用的幾種新技術都會有所瞭解,至少知道在什麼開發情境下使用什麼能夠為你節省時間提高效率。如果你覺得本文也太長了,那麼也可以考慮先收藏下來,等到有時間了再看。
簡介
首先,我們來看看工作記錄之前的介紹部分。第一節的Introduction說明了這個項目的實現結果是比較貼近真正的Google IG的,它允許拖放小組件(widget)、完全個人化的頁面、支援多頁面等。並且說明它不是一個原型項目或範例項目,而是一個真正在運作的開源的首頁:http://www.dropthings.com/。你可以在日常生活中使用它,也可以使用ASP.NET為它開發更多的小組件。
如果你看到這裡,還是不是很明白所謂的“首頁”是什麼,第二節的What is an Web 2.0 Ajax Start page給出了詳細的解釋。接著第三節的Features簡單說明了其功能,其實這些功能就和Google IG幾乎完全一樣,沒有添加任何新的東西。我覺得這個項目對比Google IG,真正應該強調的功能是你可以好像開發普通UserControl那樣為它設計小組件(widget),而不需要懂XML和JavaScript,而這正是第四節Widgets所講的。第五節Technologies說了此項目所用到的技術,看不到ASP.NET AJAX Future CTP字眼吧?那意味著你無需伸手去碰JavaScript了,AJAX實現所需要的最多就是編寫伺服器端代碼調用一下UpdatePanel和Extender。
分層
接下來從分層的角度來瞭解一下此項目的特色層,包括Web Layer和Data Access using DLinq。在Web Layer小節,作者強調的是“整個應用也就是一個頁面”,並且例如載入目前使用者設定這樣的常見任務是通過調用Workflow來完成的,還通過實測資料說明了Workflow自身的已耗用時間是很短的。
在Data Access using DLinq小節,作者強調的是使用DLing所附帶的SqlMetal.exe來直接產生資料訪問類,這些類包含了所有的資料存取碼以及實體(entity)類。然後作者拿DLing根第三方ORM工具比較,說明DLing特有的Projection技術能夠根據你所需要的欄位產生對應的屬性,這個依賴於定製編譯器的功能所以是第三方ORM工具暫時無法實現的,它給你帶來的好處就是效率的提升,因為你實際上並沒有SELECT那些你不會用到的欄位。作者還說,如果你不相信DLinq的表現,或者對DLinq抱有一種不怎麼好的感覺的話,可以直接用SQL Profiler看看DLinq讓資料庫實際執行的SQL語句到底是怎麼樣的。
第一天:使用UpdatePanel製作Widget容器
正所謂萬事開頭難,第一天要設計的Widget容器也是不容易的,別看最終產生的就是簡單的幾層嵌套div,要讓它能展開/摺疊和能被拖放,並且執行起來符合效率要求,就不那麼容易。
在這一節裡面,你需要重點注意到的就是Extender放置問題,這讓設計如何嵌套成為了一個難題。假如Extender放置在UpdatePanel內,每次UpdatePanel更新都會導致整個Extender的用戶端代碼被銷毀然後重建,也就要重新初始化一次,最終導致效率低下。因此,要盡量確保Extender都在UpdatePanel的外面,而最終的嵌套設計方案可以看作者的圖示。
至於WidgetContainer,它繼承自IWidgetHost,這種設計為將來的擴充預留了足夠的空間。當前設計的Widget,可以依賴於WidgetContainer的一些特有的功能,也可以僅僅依賴於IWidgetHost從而將來適應其他IWidgetHost容器。
第二天:製作定製的拖放Extender和多欄放置地區
在這一節裡面,作者解釋了為什麼原有的拖放Extender都不適用於此項目的情景,從而需要自己設計一個新的Extender。其實我也認為ASP.NET AJAX和Control Toolkit內建的各種拖放功能確實限制多多,並不能根據你的開發情境所需要靈活定製,所以使用ASP.NET AjAX時幾乎可以肯定如果你需要使用拖放就要自己寫。當然,這不一定需要從頭寫起,Future CTP內建了用戶端的拖放管理器,你只需要實現兩個介面就可以實現拖放。
另外大多數的Extender都是通過非同步Postback時在伺服器端儲存狀態,而這對於的首頁來說是不可能的,因為使用者一次拖放後很可能馬上就要進行下一次的拖放,所以這個定製的拖放Extender通過調用Web Service來儲存狀態。
第三天:製作資料訪問層和網站載入
作者在這一節裡展示了此項目的資料庫設計,並且說明了如何跨層地通過DLinq訪問資料。跨層使用DLinq的問題就在於,如果你的實體用於各個不同的層,那麼它就從載入它的DataContext分離(detach)出來了,通常商務邏輯層是沒有DataContext的,所以你要將它發回到資料訪問層然後利用DataContext更新,通常我們的做法是商務邏輯層修改實體然後再發回給資料訪問層,而DLinq則要求先把實體附加(attach)到DataContext再作出修改然後提交更新。
作者使用了delegate來解決這個問題,調用資料訪問層的更新函數時傳入一個delegate,然後資料訪問層把實體附加到DataContext,接著調用該delegate,最後提交更新。而該delegate指向的函數則放在商務邏輯層,這樣就保持了原來的分層,並且按照DLinq所需的方式來完成更新,雖然這導致調用在兩個層之間往返多了一次。另外,同樣的設計方式可用於插入與刪除。
第四天:使用XLinq製作Flickr照片與RSS小組件
作者想做的第一個小組件用於顯示Flickr照片。首先通過XElement把RSS載入上,然後使用XLinq將XML影射為對象集合,接著還是通過XLinq迭代集合中特定範圍的對象並且構建對應的控制項樹,就那麼簡單,通用的RSS小組件也用類似的方式製作。想象一下如果沒有XLinq你會如何去完成這部分功能,再看看作者給出的代碼,你就知道XLinq為你節省多少時間了。
值得注意的是,作者使用了HtmlGenericControl來產生連結,而不是用HtmlLink,因為HtmlLink不允許有子控制項,這是它特有的一個局限,而HtmlGenericControl就能很好的解決此問題。
第五天:製作商務邏輯層中的工作流程
這是問題多多的一天,把DLinq和WinFX混合在一起不是一件容易的事情,作者就需要解決以下問題:
- 在ASP.NET中同步調用Workflow
- Workflow運行完後將對象取出來
- 在一個Workflow同步調用另一個Workflow
第一個問題通過對WorkflowRuntime添加兩個Service解決了。第二個問題,通過建立一個叫做CallWorkflowActivity的新Activity來解決,這個Activity的實現非常複雜,有興趣瞭解其細節的朋友可以看這篇文章:A workflow sample。由於Workflow本身是設計為非同步執行的,然而ASP.NET的伺服器端處理卻要求是同步的,所以才會遇到這兩個問題。第三個問題源自Workflow是設計為可休眠的,這時候就要對它的資料進行序列化然後持久,然而DLinq的實體卻不是可序列化的。解決這個問題的方式是把DLinq實體放到Dictionary<string, object>裡面,從而通過要求對象可序列化的檢查。
另外要在同一個項目裡同時載入WinFX和DLinq的編譯器是需要通過修改專案檔的,作者給出了修改方法。不過修改後Workflow的聲明性Rules無法正常編譯,因為.rules檔案無法被正常識別為嵌入資源,這看起來是一條死路。直到第二天清晨,周圍一片寂靜,太陽準備升起,作者聽到了來自天堂的神聖啟示,然後明白到如何把這個問題解決掉,想瞭解詳情的就仔細讀讀原文吧。
第六天:頁面切換問題
頁面切換時,新頁面的小組件被第一次載入,而如果它們通過IsPostBack屬性判斷自己是否第一次載入的話,就會得到錯誤的資訊,然後嘗試根據ViewState恢複上一次的狀態也就會失敗,從而無法正常初始化。解決方案自然是通過WidgetContainer主動通知Widget這是不是你的第一次載入。
在這裡我要加插一些題外話,我覺得這不是一個有效解決問題的辦法。如果你設計一個Widget,在上面扔一個DataSourceControl和一個DataBoundControl,就可能導致載入失敗,因為這些控制項本身就設計為依賴於IsPostBack屬性,它們自動根據IsPostBack屬性來決定是否需要資料繫結。希望ASP.NET 3.0將這個問題解決了,因為我們確實會有需要動態載入DataBoundControl的時候,它們第一次載入時需要進行資料繫結,而此時IsPostBack屬性卻為true。
第七天:註冊
是時候準備迎來第一個註冊使用者了,這時候需要將他在匿名訪問時使用的設定複製到註冊使用者中去。因為匿名使用者的資訊不儲存在aspnet_membership,而僅儲存在aspnet_user和aspnet_profile,所以不能使用Membership.GetUser擷取到匿名使用者的實體然後擷取其設定,這時候就需要手動訪問資料庫完成上述複製操作了。
在使用DLinq查詢ASPNETDB之前,首先你要看Omar Al Zabir的另一篇文章:Careful when querying on aspnet_users, aspnet_membership and aspnet_profile tables used by ASP.NET 2.0 Membership and Profile provider。ASPNETDB裡面的索引都是使用ApplicationId和LowerUserName或LowerEmail組合的,為了確保查詢時通過索引以確保效率,你也必須使用上述組合作為查詢條件。
收尾
因為這個項目混合了ASP.NET AJAX、WinFX和LINQ,所以必須有一個混合型的web.config以令它們和諧共處,這正是Web.config walkthrough小節要說的事情。接著Deployment Problem和How to run the code兩個小節說明了你應該如何部署這個項目,並且讓它運行起來。之後Next Steps小節說明了你可以使用ASP.NET為此項目開發更多的小組件,而Conclusion小節則說明了混合這3種技術到一個項目裡是多麼有挑戰性的事情,不過它們也大大降低了你的開發難度。
總結
中間我跳過了How slow is ASP.NET Ajax這一小節沒說,在這裡作者說明了當一個頁面的UpdatePanel和Extender的數量增加到一定程度時,用戶端的執行速度將變得非常低。後來作者根據Scott Guthrie的提醒,在web.config中設定debug="false",這將關閉用戶端指令碼運行時的驗證功能,執行速度已經比較好,在IE7、FF和Opera9中速度有明顯的提升,然而在IE6中還是比較慢。
這說明了對於只有一個頁面的AJAX應用來說,使用UpdatePanel和Extender並非是一個很好的解決方案,除非你有足夠的能力通過各式hacking來提升執行效率。然而我覺得,如果你有那樣的能力,乾脆就用Future CTP開發以用戶端為中心的AJAX應用好了,可以盡量減少對伺服器端控制項與Extender的依賴。