接上篇(通用性站點管理后台(Bee OPOA Platform) (2)- 快速開發特性)
當時在系統構建的一開始就想引入MVC特性, 本人比較偏向於這種方式, 對Asp.net 基於事件這種方式不是特別興趣。 對純粹的http的調用方式很喜歡(可以用Fiddler攔截, 以便查找原因), 以最基本的Get/Post方式及請求的參數列表, 則可以很清晰的知道該請求與服務器端對應的關系。
配置
該特性開發目前基於以下配置文件展開的(IIS 7跟這個有點不一樣, 具體可以參看Codeplex項目中的web.config文件):
<add verb="*" path="*/*.bee,*.*.bee" type="Bee.Web.AuthMvcDispatcher, Bee.Security" validate="true" />
Bee.Web.AuthMvcDispatcher類型集成了RBAC權限中的權限判斷, 會在前端發起請求的時候統一判斷該請求是否有權限, 若沒有, 則返回一個Json結構的結果。該類型繼承與Bee.Web.MvcDispatcher。 Bee.Web.MvcDispatcher是繼承接口有IHttpHandler,IRequiresSessionState,該Handler將處理所有后綴為bee的請求。
Action執行
目前只支持相當於Asp.net MVC框架中的最簡單的路由規則, 即/{ControllerName}/{ActionName}.bee的方式。 獲取了ControllerName, 就要獲得對應的Controller類型。
如何獲取ControllerName對應的Controller類型呢? 這個往往是先收集程序集中所有可能的Controller類型, 即掃描所有程序集放到一個對應的Dictionary集合中, 如:
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()) { string s = a.FullName.Split(',')[0]; if (!s.StartsWith("System.") && CouldBeControllerAssemebly(s)) { foreach (var type in assembly.GetTypes()) { if (!type.IsInterface && !type.IsAbstract && type.IsSubclassOf(CbType) && !type.IsGenericType && type.Name.EndsWith("Controller")) { // 符合就放入對應的ControllerName與類型的集合 } } } }
有心的人很容易看出來這里有個問題, 什么問題呢? 就是不同NameSpace相同ControllerName的問題。 這個的問題解決與否, 其實取決於系統的應用, 若不是設計像微軟這樣做基礎性工作的設計的話, 完全可以不需要考慮該情形。 您認同嗎? 本平台對該類情形未作考慮。對應關系找出來后, 生成一個Controller實例就無問題了。 現在問題轉化到:給你一個類, 根據摻入的方法名及參數, 請動態調用盡可能合適的方式。
回頭看《【討論】一個接口的世界》, 就相當於要實現方法 object Invoke(object entity, string methodName, BeeDataAdapter dataAdapter);
那實現該方法的關鍵在於哪里呢?本人的解決方案的是通過Emit動態生成對應的代理類。 下面將結合實例(該實例可通過http://beeopoa.codeplex.com獲得, 通過svn方式可獲得源碼)。
我建立如下的Controller:
public class MVCTestController : ControllerBase { public int Add(int i, int j) { return i + j; } public int Add(int i) { return i; } public int Add(int i, BeeDataAdapter dataAdapter) { return 100; } }
對應生成的ControllerProxy的代碼(該代碼由於是動態產生, 可通過Reflector查看\Bee.OPOADemo\Cache\Bee_Core_EntityProxy\BeeCoreEntityProxy.dll獲的。 該dll為程序運行時產生, 在整個應用程序關閉時, 會生成該dll, 以便調試及查看用。)
public class MVCTestControllerProxy : EntityProxy<MVCTestController> { // Methods public override object GetPropertyValue(object obj1, string text1) { MVCTestController controller = (MVCTestController) obj1; if (text1 == "ControllerName") { return controller.ControllerName; } return null; } public override object Invoke(object obj1, string text1, BeeDataAdapter adapter1) { MVCTestController controller = (MVCTestController) obj1;
// 該方法為具體的匹配方法, 該方法已被混淆
A2MkIq94Rgjx rgjx = ReflectionUtil.ABGkd(typeof(MVCTestController), text1, adapter1); string str = rgjx.AAkp5A9X; BeeDataAdapter aBGkd = rgjx.ABGkd; switch (str) { case "Int32 Add(Int32, Bee.BeeDataAdapter)": return controller.Add((int) ConvertUtil.Convert(aBGkd["i"], typeof(int)), (BeeDataAdapter) ConvertUtil.Convert(aBGkd["dataAdapter"], typeof(BeeDataAdapter))); case "Int32 Add(Int32, Int32)": return controller.Add((int) ConvertUtil.Convert(aBGkd["i"], typeof(int)), (int) ConvertUtil.Convert(aBGkd["j"], typeof(int))); case "Int32 Add(Int32)": return controller.Add((int) ConvertUtil.Convert(aBGkd["i"], typeof(int))); } return null; } public override void SetPropertyValue(object obj1, string text1, object obj2) { MVCTestController controller = (MVCTestController) obj1; text1 = text1.ToLower(); if ((obj2 != null) && (obj2 != DBNull.Value)) { } } }
該類的方法GetPropertyValue及SetPropertyValue是針對設計ORM時或者在序列化反序列化的場合下有用, 該篇將講解Invoke方法。
由於方法名可能相同, 但參數不同的情形。 及c#中關鍵詞out及ref的引入, 對該問題的處理有點繁雜, 我們只關注最需要關注的那部分, 首先在設計之初就不考慮對out及ref的支持。
那么問題基本就回歸到參數匹配的問題, 思路如下: 1. 參數匹配以名稱匹配為准, 簡單類型為主;2.以參數多的開始匹配;3. 復雜類型這參數列表中最多只能有一個。
若能匹配則調用對應的方法, 執行。
Action執行返回值的處理
平台提供了一個ActionResult的基類, 並且提供了擴展類ContentResult(直接發送內容, 如直接的文本), JsonResult(實例的Json格式), PageResult(視圖), RedirectResult(頁面跳轉)
StreamResult(流類型,文本, 或者圖片)。
並且在Controller的基類ControllerBase提供了內置方法。 如Json, View。 基本上都還比較好理解, 轉化一下加入Response的流中輸出。 如何執行對應的PageResult呢? 如何使用不同的視圖引擎來渲染呢? 說白了, 其中的過程就相當於一段文本(View, aspx文件, cshtml文件以及其他), 然后有很多值(Model, 及ViewData 或者諸如其他), 然后得到最終輸出的結果。 呵呵, 很像基於模板的代碼生成器, 。 最經典的莫過於Asp.Net WebForm引擎, 本平台目前也只實現基於此的視圖引擎, Razor方式, 及NVelocity, 暫未實現。 因為該方式最簡單及Framework中內置的:
object o = System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(object));
該方法直接創建基於路徑的page實例, 其中的緩存及路徑查找你都不用考慮。 若要實現自己的引擎則需要考慮這些。
總結
c#中實現MVC的方式其實很簡單。 關鍵是在實現《【討論】一個接口的世界》的時候, 要注意性能。