應用程式框架實戰三十三:表現層及ASP.NET MVC介紹(二)
最近的更新速度越來越慢,主要是專案上比較忙,封裝EasyUi也要花很多時間。不過大家請放心,本系列不會半途夭折,並且程式碼乾貨也會持續更新。本文繼續介紹表現層和Asp.net Mvc,我將在本篇討論一些重要的設計問題和封裝技巧。
是否需要將控制器分離為獨立專案
經常有人問我,是否有必要將控制器從Web專案中分離出來,下面談一下我的認識,僅供你參考,不一定正確,請根據你自己的實際情況決定。
控制器的作用是呼叫業務邏輯,將獲得的結果傳給檢視顯示。從根本上說,控制器只是起協調作用,它不應該自身完成複雜的業務邏輯。
不過哪怕你將業務邏輯儘量放到了領域層,控制器上還是會有不少程式碼,比如查詢條件的設定,事務控制等。在這種情況下,將控制器分離到一個獨立的程式集,似乎說得過去。
但是分離控制器程式碼有很多方法,比如引入應用層服務,應用層服務可以看成是控制器的延伸,控制器上大部分程式碼被轉移到應用層服務中,控制器變成很薄的一層,這種情況下,分離控制器到獨立專案,沒有任何價值。
將控制器分離到一個獨立程式集後,你會發現查詢控制器對應檢視變得更加困難,哪怕Resharper可以幫你定位跳轉。從目錄結構上看,也不再遵循大家熟悉的約定,實際上降低了可維護性,增加了學習成本。
最後的結論就是,如果你採用了應用層服務,就不要分離控制器。
是否需要將MVC專案拆分為多個
對於表現層的模組化,MVC提供了Areas(區域)技術來支援較大專案的開發。每個區域包含獨立的控制器和相關檢視,為每個業務模組建立一個區域,對於一般專案的管理,應該都夠用了。
我在學習一些流行的應用開發框架時發現,有些應用開發框架將同一個Asp.Net Mvc專案,拆分為多個Mvc專案,以實現網站級別的模組化。其中一個是主Web專案,包含大部分WEB資源,比如圖片、CSS、JS等。其它一些附屬Web專案,包含其它業務模組,如果包含js檔案,該檔案的生成操作需要設定成“嵌入的資源”,包含的cshtml檔案還需要用RazorGenerator外掛手工生成類。最後主Web專案引用其它附屬Web專案,從而整合為一個網站。
這種架構的好處是允許複用業務UI模組,包括檢視,同時方便團隊協作,不同成員開發不同模組時干擾更小。
但是這種架構也有很多問題,將JS設定成“嵌入的資源”,cshtml檔案使用RazorGenerator生成類,會導致JS和頁面除錯部署都很不方便,除錯時經常會碰上快取問題。
解決UI複用和協同工作的另一個辦法是,多個網站獨立開發部署,採用單點登入連線成一個系統。
對錶現層的模組化建議如下:普通專案採用單Mvc專案架構,通過Areas進行模組化, 更大專案採用多Mvc專案架構,單獨開發部署,以單點登入方式整合,儘量不要將js、cshtml等頁面元素嵌入程式集,會導致更難維護,僅在具有特殊需求時採用,比如外掛式開發。
Asp.Net Mvc抽象封裝技巧
對於技術和業務邏輯,我們進行了複雜的分層和封裝,那麼對於頁面本身,還要不要抽象?
Html由標籤組成,大量的Html標籤,讓你無法清晰的看出頁面結構。當需要修改頁面上某個區域時,如果頁面很長很複雜,你需要在雜亂的Html標籤中來回掃描,以定位目標,這與幾百行的長方法類似。有經驗的開發人員,總是以單一職責原則SRP為指導,通過提取方法和組合方法重構,保持方法的簡短,同時獲得清晰的程式碼結構。
除了定位困難以外,未進行抽象的頁面,將包含大量重複的Html,而冗餘程式碼是可維護性的天敵。
對於Asp.Net WebForm,抽象封裝主要依賴母版頁、伺服器控制元件、使用者控制元件等元素。
母版頁用於管理通用頁面結構,幫助劃分區域,它類似於抽象類,可以提供一部分具體實現,它將多個頁面共享的部分抽取上來,並提供了內容佔位符,佔位符類似於虛方法或抽象方法,以方便具體頁面填充內容。
伺服器控制元件用於封裝能夠高度複用的UI元素,提供增強功能,比如自定義文字框,一般的文字框只能輸入文字,自定義文字框可以進行驗證,甚至能進行許可權控制等。
使用者控制元件是比伺服器控制元件更輕量的封裝方式,使用者控制元件一般用於封裝頁面上的元素或區域。母版頁的工作方式類似於繼承,有經驗的同學知道,繼承的靈活性是比較低的,所以設計上流傳一句話“多用委託,少用繼承”,WebForm的“委託”就是使用者控制元件,它同樣可以切割頁面,並且提供了更好的靈活性。
如果很多頁面都需要分成上中下三個區域,每個區域有一些固定的元素,這時候採用母版頁就是合適的。但如果只有一個或幾個頁面需要這個結構,採用母版頁將是大炮打蚊子,使用者控制元件則是更好的選擇。
使用者控制元件除了能夠封裝Html以外,還包含後置程式碼,可以處理邏輯。
瞭解了WebForm的基礎封裝元素後,再來看Asp.Net Mvc提供了哪些元素,與Web Form的元素是如何對應的。
首先看母版頁,母版頁的功能是提供佈局結構,Mvc提供了佈局頁來支援類似功能。開啟Mvc專案Views\Shared目錄,會發現一個名為_Layout.cshtml的檔案,這是系統定義的佈局頁,_Layout這個名稱不是必須的,它能夠起作用的原因是Views目錄下的_ViewStart.cshtml設定指向它。不過沒事不要亂改系統定義的檔名,這樣會破壞約定,增加維護成本。
_ViewStart.cshtml包含了Layout的設定,它的作用是為檢視提供預設佈局設定,值得一提的是,根目錄Views下的_ViewStart.cshtml設定不能繼承,Areas各模組必須新增自己的_ViewStart.cshtml。
對於較複雜的系統,僅依靠一個_Layout.cshtml來抽象頁面冗餘是不夠的,一般需要多層繼承結構,_Layout.cshtml僅放置通用性很強的內容,比如js,css引用等,更具體的抽象可定義自己的佈局頁,繼承_Layout.cshtml。
Asp.Net Mvc不再提供伺服器控制元件這種視覺化元素,但相關的封裝思想卻從未間斷。Mvc提供了HtmlHelper、UrlHelper、AjaxHelper等幾個幫助類來封裝相關操作,其中HtmlHelper包含表單元素的封裝,是與WebForm伺服器控制元件相對應的東西,我們封裝控制元件主要從它下手。
Mvc允許在檢視中通過Html屬性訪問HtmlHelper,可以看到,所有控制元件都是通過擴充套件方法的方式新增上去的,這也給我們提供了一種思路。我在前文已經多次提到,使用擴充套件方法要很小心,特別是擴充套件系統的類,因為可能造成大面積汙染。
對於HtmlHelper,我一般僅擴充套件少量方法,首先是通用UI技術的封裝,我會把需要在檢視上用到的通用技術封裝到@Html.X()方法中,比如匯入一個Js檔案,可以這樣呼叫@Html.X().ImportJs(“xx”),當然現在匯入Js一般用Bundle,本篇後續再介紹。
其次是對特定UI技術的封裝,比如Dwz,EasyUi,Ext等,也可能是其它元件,比如圖表FusionCharts,ECharts等。對於每一個要用到的元件,都僅為其在HtmlHelper擴充套件一個方法,以EasyUi為例,你不能這樣擴充套件,@Html.EasyUiTextBox(),@Html.EasyUiCombox(),這樣會在HtmlHelper中擴充套件大量方法,導致查詢其它方法變得困難,更好的方法是@Html.EasyUi().TextBox() ,@Html.EasyUi().Combox()。這裡設計的核心是EasyUi方法返回一個介面,將EasyUi所有操作全部放到這個介面中。關於Mvc的控制元件封裝,我會在後續EasyUi系列詳細講述。
最後,需要在HtmlHelper中擴充套件的是業務UI元件,比如字典、省市區三級聯動、人員選擇等,我會把所有業務UI元件擴充套件到@Html.Ui()方法中,以方便開發某些業務模組時複用,比如字典@Html.Ui().Dic()。
下面來看WebForm使用者控制元件在Mvc中有哪些對應元素。
如果頁面中的某個區域很複雜,根據邏輯結構,將區域分解為更小的部分,每個部分放到一個使用者控制元件中,從而得到清晰的結構。
Mvc提供的@Html.Partial()方法允許將Html分離到部分檢視中,並可以給這個部分檢視傳遞一個實體,以進行資料繫結。
不過部分檢視的能力是有限的,你的主檢視必須能夠提供部分檢視相關資料,這就要求主檢視的實體攜帶更多的資料,這在很多時候都不太方便。打個比方,你的頁面上需要顯示一個下拉人員列表,列表的內容是用另一個表的資料填充的,如果採用部分檢視來封裝這個列表,你的主檢視對應的實體就必須提供人員集合。
Mvc提供了更為強大的@Html.Action()方法,Action也用於操作部分檢視,但它能夠獨立的為檢視提供資料。還是剛才的下拉人員列表,現在主檢視的實體不再需要攜帶人員資料了,呼叫Action後,人員列表已載入完成。
雖然Action更強大,但它需要設定Url資訊,以確定這個功能由哪個控制器的方法提供,當某個Action操作用得非常頻繁時,考慮將該操作擴充套件到HtmlHelper中,這樣可以封裝掉Url和引數資訊,以簡化呼叫。
以上簡單介紹了Mvc的一些封裝元素,以供你編寫出更清晰的UI程式碼。同時比較了Asp.net WebForm與Mvc的元素對應情況,你如果具有WebForm的基礎,相信Mvc的封裝也會很快上手。
補充一點,雖然我用方法與Html長度類比,但不能認為Html的封裝粒度越細越好,我曾經嘗試過很細粒度的UI拆分,最終效果並不理想,合適的拆分粒度更好維護,這方面根據自己的習慣進行摸索。
Bundle介紹
現在的系統Js和Css檔案都很多,如果一個網站引用太多Js或css檔案,對效能是有一定影響的,因為每個檔案會發送一個請求。
我以前的辦法是使用AjaxMinifier工具手工壓縮Js,再手工合併,Css也採用類似辦法,後面使用了一個第三方的工具,也比較麻煩。
Asp.Net為此提供了一個叫Bundle的打包壓縮技術,它能夠在執行時將js或css檔案打包壓縮,這正是我需要的。
不過在使用過程中,發現它並不是想像中那麼易用,問了一些人,也用起來有問題。還有一些人沒碰到啥問題,但觀察他的程式碼,實際上沒有起作用,因為他沒有設定啟用優化的選項。
使用Bundle有幾點需要注意:
- 如果檔案中的程式碼對路徑很敏感,比如css中用了相對路徑,你在配置Bundle時,虛擬路徑就不能很隨意,因為會破壞路徑關係,導致失敗。
- 如果沒有在程式碼中設定BundleTable.EnableOptimizations = true,也沒有在web.config進行相應配置,則打包壓縮不會起作用,只是讓你在引用檔案時省點力。
- 已經壓縮過的檔案,比如jquery.min.js,不要用Bundle配置,使用常規方式引入,不然執行時可能出錯。
Util最新程式碼示例更新
除了之前的大量程式碼已重構外,主要更新了EasyUi的行內編輯方式。
結束語
本文簡單介紹了Mvc相關的一些問題和技巧,有不同意見,歡迎交流。
下載地址:下載時請順手推薦,以支援本人寫作.
http://files.cnblogs.com/files/xiadao521/Applications.2015.3.16.1.rar
http://files.cnblogs.com/files/xiadao521/Framework.2015.3.16.1.rar
http://files.cnblogs.com/files/xiadao521/Data.2015.3.5.1.rar
注意:本人每次釋出新版本時,會刪除老版本
.Net應用程式框架交流QQ群: 386092459,歡迎有興趣的朋友加入討論。
.Net Easyui開發交流QQ群(本群僅限Easyui開發者,非Easyui開發者勿進):157809322
謝謝大家的持續關注,我的部落格地址:http://www.cnblogs.com/xiadao521/