用過和做過插件的都會了解插件的好處,園子里也有很多和討論,但大都只些簡單的加載程序集什么的,這里主要討論的就是使用 ASP.NET MVC 4 來實現每個插件都可以完全從主站點剝離出來,即使只是一個插件,也是一個完整的站點,同時也可以和其它插件一起組裝成一個龐大的系統。
參考資料:
ASP.NET MVC 4 源碼。
Orchard 源碼。
MVC3PlugInDemo 源碼。
ASP.NET MVC的Razor引擎:View編譯原理
基於ASP.NET MVC3 Razor的模塊化/插件式架構實現
基於OSGi.NET開發ASP.NET MVC 3.0插件化應用程序
http://stackoverflow.com/questions/6923572/asp-net-mvc-3-portable-area-view-doesnt-find-my-model
首先,非常感謝以上幾位大牛分享的文章,由於文筆不好,對.NET 了解也不夠,希望大家多多指點。
理想情況下是希望能夠像Orchard那樣,可以運行時修改代碼,又或者可以直接把插件(包含頁面、樣式、圖片等資源文件)編譯成一個DLL來使用(但是這樣的做法會對前端與美工修改不便,而且就算改一個字也要重新編譯一個DLL),只是依然還沒找到方法ORZ。
最終結構圖如下。
當把插件的站點發布出來后,目錄名為插件名,並將該目錄及目錄下的所有文件復制到Plugins目錄下即可自動安裝並運行,不需要重啟程序池。
要實現這么一個架構,最初認為,只要使用 Assembly.LoadFile(name);方法來加載外部的程序集,並且使用反射創建控制器,在定義一下MVC的模板引擎的搜索路徑不就可以了嗎?
當具體實現之后,發現,在不使用強類型的模型綁定時,可以正常使用,但是,使用了強類型的模型綁定時,則會出現以下錯誤。
問題產生原因:
.NET 會把.cshtml 與相關的程序集進行編譯,之后訪問的是編譯后的臨時程序集,但是,由於沒有引用進入系統中,這里編譯的時候沒有該程序集,就會出現錯誤。
那么,要解決這個問題,就需要在編譯時,把需要的程序集,都一起編譯了,但是,怎么樣才可以實現?
直接引用並使用類庫的時候,系統會自行編譯到一起了,所以解決辦法有兩種:
1、在系統啟動前的預編譯時,手動把相關的程序集增加進系統中,這樣就是一個實際存在於系統的程序集,在頁面編譯時自然會編譯進去。
在google查找相關的解決方法時,發現了該方法:
BuildManager.AddReferencedAssembly(assembly);
在查MSDN有這么一段話:此方法必須在 Global.asax 文件中的 Application_Start 事件發生前調用。
也就意味着加載程序集的方法就必須要在預啟動階段就是加載了。
並且使用上面的方法,來把程序集加到系統里。
雖然這樣可以正常使用了,但是,偶爾還是會有出現編譯錯誤的異常。
在調試階段下,只有重新生成的代碼時可以正常運行,重新生成之后的代碼,在點啟動調試時,就會出現編譯錯誤問題,調試發現,在這個時候,系統並沒有將需要的程序集加載到系統中,有大牛了解的話希望指點下原因。
但是,在使用了Web.config 配置文件中的節點“probing”以后,把相關的程序集復制到“probing”指定的目錄下,就能正常運行了。
但是, 由於上面的那段代碼只可以在預啟動階段使用,所有注定了該方法有個缺點,就是每個更新插件時,都要重啟或者回收一次程序池,沒能正常的做到插件化的靈活性。
2、在模板(cshtml)進行編譯前,把外部引用的相關的程序集增加到編譯信息中,這樣在對模板進行編譯的同時,會把該程序集也編譯進去。
在谷歌娘的幫助下,找到了該事件:
RazorBuildProvider.CodeGenerationStarted
從名字可以看出,這是在編譯啟動時觸發的事件,具體功能不明ORZ。
可以通過該事件,把外部的程序集增加到 RazorBuildProvider 類中。
provider.AssemblyBuilder.AddAssemblyReference(plugin.Assembly);
provider 是 RazorBuildProvider 類的一個實例。
plugin.Assembly 是一個頁面所使用的程序集。
只要把這段代碼放到模板引擎的搜索視圖的位置,即可根據需要,將增加外部的程序集,由於重復增加會出現已添加組件異常,所以,這里加了個 isLoadAssembly 變量來確認是否已增加過。
/// <summary> /// 給運行時編譯的頁面加了引用程序集。 /// </summary> /// <param name="pluginName"></param> private void CodeGeneration(string pluginName) { RazorBuildProvider.CodeGenerationStarted += (object sender, EventArgs e) => { RazorBuildProvider provider = (RazorBuildProvider)sender; var plugin = PluginManager.GetPlugin(pluginName); if (plugin != null) { provider.AssemblyBuilder.AddAssemblyReference(plugin.Assembly); } }; }
我不喜歡每裝一個插件,都要重啟一次,所以我選擇使用了第二種方法。
下一篇,將使用第二種方法來進行具體的實踐並發布源碼。