這篇文章主要為大家詳細介紹了使用ASP.NET MVC引擎開發外掛程式系統的相關資料,具有一定的參考價值,感興趣的小夥伴們可以參考一下
一、前言
我心中的外掛程式系統應該是像Nop那樣(更牛逼的如Orchard,OSGI.NET),每個外掛程式模組不只是一堆實現了某個業務介面的dll,然後採用反射或IOC技術來調用,而是一個完整的mvc小應用,我可以在後台控制外掛程式的安裝和禁用,目錄結構如下:
產生後放在網站根目錄下的Plugins檔案夾中,每個外掛程式有一個子檔案夾
Plugins/Sms.AliYun/
Plugins/Sms.ManDao/
我是一個有強迫症的的懶人,我不想將產生的dll檔案拷貝到bin目錄。
二、要解決的問題
1.asp.net引擎預設只會載入“bin”檔案夾中的dll,而我們想要的外掛程式檔案則是分散在Plugins目錄下的各個子目錄中。
2.視圖中使用了模型時如何處理?預設情況下RazorViewEngine使用BuildManager將視圖編譯成動態程式集,然後使用Activator.CreateInstance執行個體化新編譯的對象,而使用外掛程式dll時,當前的AppDomain不知道如何解析這種引用了模型的視圖,因為它不存在於“bin”或GAC中。更糟糕的是,不會收到任何錯誤訊息,告訴您為什麼它不工作,或者問題在哪。相反,他會告訴你,從View目錄中找不到檔案。
3.某個外掛程式正掛在網站下運行著,直接覆蓋外掛程式的dll,會告訴你當前dll正在使用,不能被覆蓋。
4.視圖檔案不放網站的View目錄中,該如何載入。
三.Net 4.0讓這一切變成可能
Net4.0有一個新特性是在應用程式初始化之前執行代碼的能力(PreApplicationStartMethodAttribute),這個特性使得應用程式在Application_Star前可以做一些工作,例如我們可以在應用啟動之前告知我們的mvc外掛程式系統的dll放在哪,做預先載入處理等。關於.net的幾個新特性,有歪果仁寫得有部落格來介紹,點我。,關於PreApplicationStartMethodAttribute,有博友已經寫過了,點我。 Abp的啟動模組應該也是使用PreApplicationStartMethodAttribute這個特性原理來實現的,具體是不是這樣還沒看。
四、解決方案
1.修改主要站台web.config目錄,讓運行時除了載入bin目錄中的檔案,還可以從其它目錄載入
<runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="Plugins/temp/" /> </assemblyBinding> </runtime>
2.開發一個簡易的外掛程式管理類,這個類的作用就是在Application_Start之前就把Plugins各子目錄中的dll拷貝到第1步指定的檔案夾中,為了讓demo儘可能簡單,沒有對重複的dll進行檢測(比如外掛程式中引用了ef程式集,主要站台也引用了,在網站bin目錄中已經存在ef的dll了,就沒必要再把外掛程式中的dll拷貝到上面設定的動態程式集目錄中)
using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Reflection;using System.Text;using System.Threading.Tasks;using System.Web;using System.Web.Compilation;using System.Web.Hosting;[assembly: PreApplicationStartMethod(typeof(Plugins.Core.PreApplicationInit), "Initialize")]namespace Plugins.Core{ public class PreApplicationInit { static PreApplicationInit() { PluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins")); ShadowCopyFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins/temp")); } /// <summary> /// 外掛程式所在目錄資訊 /// </summary> private static readonly DirectoryInfo PluginFolder; /// <summary> /// 程式應行時指定的dll目錄 /// </summary> private static readonly DirectoryInfo ShadowCopyFolder; public static void Initialize() { Directory.CreateDirectory(ShadowCopyFolder.FullName); //清空外掛程式dll運行目錄中的檔案 foreach (var f in ShadowCopyFolder.GetFiles("*.dll", SearchOption.AllDirectories)) { f.Delete(); } foreach (var plug in PluginFolder.GetFiles("*.dll", SearchOption.AllDirectories).Where(i=>i.Directory.Parent.Name== "plugins")) { File.Copy(plug.FullName, Path.Combine(ShadowCopyFolder.FullName, plug.Name), true); } foreach (var a in ShadowCopyFolder .GetFiles("*.dll", SearchOption.AllDirectories) .Select(x => AssemblyName.GetAssemblyName(x.FullName)) .Select(x => Assembly.Load(x.FullName))) { BuildManager.AddReferencedAssembly(a); } } }}
3.如何讓View引擎找到我們的視圖呢?答案是重寫RazorViewEngine的方法,我採用了約定大於配置的方式(假設我們的外掛程式項目命名空間為Plugins.Apps.Sms,那麼預設的控制器命名空間為Plugins.Apps.Sms.Controllers,外掛程式產生後的檔案夾必須為/Plugins/Plugins.Apps.Sms/),通過分析當前控制器就可以知道當前外掛程式的View目錄位置
using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using System.Web;using System.Web.Mvc;using System.Web.WebPages.Razor;namespace Plugins.Web{ public class CustomerViewEngine : RazorViewEngine { /// <summary> /// 定義視圖頁所在地址。 /// </summary> private string[] _viewLocationFormats = new[] { "~/Views/Parts/{0}.cshtml", "~/Plugins/{pluginFolder}/Views/{1}/{0}.cshtml", "~/Plugins/{pluginFolder}/Views/Shared/{0}.cshtml", "~/Views/{1}/{0}.cshtml", "~/Views/Shared/{0}.cshtml", }; public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { string ns = controllerContext.Controller.GetType().Namespace; string controller = controllerContext.Controller.GetType().Name.Replace("Controller", ""); //說明是外掛程式中的控制器,View目錄需要單獨處理 if (ns.ToLower().Contains("plugins")) { var pluginsFolder = ns.ToLower().Replace(".controllers", ""); ViewLocationFormats = ReplacePlaceholder(pluginsFolder); } return base.FindView(controllerContext, viewName, masterName, useCache); } /// <summary> /// 替換pluginFolder預留位置 /// </summary> /// <param name="folderName"></param> private string[] ReplacePlaceholder(string folderName) { string[] tempArray = new string[_viewLocationFormats.Length]; if (_viewLocationFormats != null) { for (int i = 0; i < _viewLocationFormats.Length; i++) { tempArray[i] = _viewLocationFormats[i].Replace("{pluginFolder}", folderName); } } return tempArray; } }}
然後在主要站台的Global.asax中將Razor引擎指定為我們重寫過的
4.開始製作一個外掛程式目錄,跟我們平時建立的MVC項目並沒有太大區別,只是發布時需要做一些設定。
.產生路徑要按照第3條的約定來寫,不然會找不到視圖檔案
.View目錄下的web.config和.cshtml檔案要複製到組建目錄(在檔案中點右鍵)
3.設定引用項目中的產生屬性,主程式下面已經有了的就把“複製到輸出目錄”設定為無,要不然拷貝到動態bin目錄時會出錯,可以對第2步中的那個類改造一下,加入檔案比較功能,bin目錄中沒有的,才拷貝到動態bin目錄中。
4.產生後的目錄結構如下:
5.跑一下,一切正常,外掛程式中的控制器工作正常,視圖中引用了Model也沒問題
到此,一個外掛程式系統的核心部分就算完成了,你可繼續進行擴充,增加外掛程式的發現、安裝、卸載功能,這些相對於核心功能來說,都是小兒科。後續我會基於Abp架構出一個外掛程式系統的文章,有興趣的把小板凳準備好,瓜子花生買上:)