ASP.NET MVC + ADO.NET EF 項目實戰(一):應用程序佈局設計

什麼叫上下文?
在你設計一個方法的時候,無法直接從方法參數或實例成員(字段或屬性)獲得的所有信息都是上下文。例如:
  • 當前用戶是誰?
  • 剛纔提供操作的數據庫連接實例從哪裏拿到?
  • 這個方法從哪個 View 或者哪個 Controller 調用的?
當然,在方法體中獲得上下文最終還是要靠方法參數或實例成員。
在MVC中有大量的上下文信息,例如:
  • ControllerContext
  • ViewContext
  • ModelBindingContext
  • ExceptionContext
  • ActionExcutingContext
  • ActionExcutedContext
  • AuthorizationContext
  • ResultExcutingContext
  • ResultExcutedContext
這些上下文通過單一的參數提供了豐富的運行時信息。
實體上下文放到哪裏?
除了MVC的上下文外,還有一個重要的上下文就是 ADO.NET EF的實體上下文,通常派生自System.Data.Objects.ObjectContext,都是由IDE自動生成的。這個上下文承載了數據庫連接,需要通過IDisposable來釋放連接。多數情況下,我們這樣使用:
using(MyEntities context = new MyEntities())
{
…… 在這裏寫入代碼
}
如果在一次頁面生命週期內只使用一次實體上下文這樣處理是非常合適的,但是事實上不都是這樣。更多的時候可能需要臨時對實體進行一個小的訪問,例如獲得一個當前用戶的顯示名,通過這種方式訪問就代價太大了。
我們知道,這個上下文可以存放到HttpContext裏。在HttpContext的所有容器中,只有Items是最合適的,因爲這個屬性的存續期在後臺頁面對象釋放後就結束了。當然,被釋放時也不會執行IDisposable的Dispose方法。我們仍然需要在Global.asax中捕捉EndRequest事件。但是奇妙的是:在ASP.NET MVC Application中不能使用event方式來捕捉,只能手工寫Application_EndRequest方法
什麼是一次Model、二次Model和Form Model?
Model一共分爲三種:
  • 直接數據庫實體映射實例,如Product(產品)
  • 爲View的呈現提供服務的包裝對象,如ProductInfo(產品信息)
  • 爲Post回傳提供服務的包裝對象,如ProductForm(產品屬性值)
第一種類型Model的特點是非常濃縮,幾乎沒有冗餘,通過複雜的關係進行組合,通常需要通過多個不同類型的實例進行組合來表達一個完整的有意義的場景。例如,一個產品信息可能包含產品名稱、產品類別、該產品所有的規格型號以及每種規格型號的參數、單價等。雖然ADO.NET EF提供了獲取組合屬性的能力,但不能處理多層次,並且不能對加載過程進行控制。所以,需要專門定義一些Model對這一組Model進行包裝。如果把原始的模型稱做“一次Model”,則可以把這個包裝對象稱做“二次Model”。
頁面上收集到的Form信息,通過三種方式傳遞到Controller(以登錄爲例):
  • 每個信息項一個參數:public ActionResult Login(string userName, string password){…}
  • 一個單一的名值對參數:public ActionResult Login(FormCollection formCollection){…}
  • 一個單一的包裝對象:public ActionResult Login(LoginInfo info){…}
第一種方式不利於重構。當需要加入一個參數時,必須修改Action的簽名。而且也無法令Controller把值傳遞到View。第二種方式不利於設計時糾錯,因爲FormCollection中的值不是強類型的。所以,我們通常都會採用第三種方式。雖然ADO.NET EF對象可以直接作爲Form Model,並且有BindAttribute對屬性與Form值進行定製化的綁定,但是不夠靈活,如果一個Form組合跨多個一次Model類型,則根本無法處理。所以我們有必要專門定義一個Model給View使用。我們不妨稱之爲“Form Model”。
業務邏輯放到什麼地方?
MVC是一種“古老”的設計模式,提供了非常自然的分層方式,這也是爲什麼利於單元測試的原因。除了MVC這些“主層”以外,BLL可以算是一個“亞層”。那麼,我們把BLL放到什麼地方最合適?
BLL需要完全可見Model層,同時也需要一些上下文信息。例如,我們至少從我們剛纔描述的論題中發現,需要從HttpContext的Items中獲得實體上下文。有些時候,我們還需要將用戶的一些登錄信息緩存到HttpContext中,如果用戶的登錄信息非常複雜的話,僅僅依靠HttpContext.User.Identity.Name每次去抓取未必很合算。我的習慣是把和這個用戶相關的信息組合到一個大的Model對象中,並把這個對象的實例 存放到HttpContext.Cache中。如果有任何變化,釋放這個Cache項即可。
所以,對於業務邏輯的位置你可以有兩個選擇:
  • 放到 Model 下,再建立一個“上下文提供器接口”,由 Model 藉助上下文來獨立處理。
  • 放到 Controller 下,直接使用 Controller 提供的上下文來進行處理。
第一種方式不依賴Controller,解耦徹底,非常靈活,更易於測試。但是需要付出一定的成本,調用棧會稍深一點,還需要勞神處理到Controller與BLLContext間的關係。第二種方式解耦不夠徹底,但非常簡捷,比較適合 Controller 與 Model 不必徹底解耦的小型項目。有意思的是:ASP.NET MVC Application模板所生成的AccountController採用的就是第二種方式。
ADO.NET EF僅影響ASP.NET MVC的Model層。在Model層中除了EDMX自動生成的一次Model外,我們還需要建立大量的二次Model和Form Model。當然,從提升內聚度考慮,所有的業務邏輯方法都在這些Model中定義,特別是,可以利用partial類和擴展方法這兩種手段加入業務邏輯。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章