最近的更新速度越來越慢,主要是項目上比較忙,封裝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/