什麼叫上下文?
在你設計一個方法的時候,無法直接從方法參數或實例成員(字段或屬性)獲得的所有信息都是上下文。例如:
當前用戶是誰?
剛才提供操作的數據庫連接實例從哪裡拿到?
這個方法從哪個 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外,我們還需要建立大量的二次Mod
el和Form Model。當然,從提升內聚度考慮,所有的業務邏輯方法都在這些Model中定義,特別是,可以利用partial類和擴展方法這兩種手段加入業務邏輯。