先回顧一下上篇決定的作法:html
一、定義程序集搜索目錄(臨時目錄)。架構
二、將要使用的各類程序集(插件)複製到該目錄。app
三、加載臨時目錄中的程序集。框架
四、定義模板引擎的搜索路徑。ide
五、在模板引擎的查找頁面方法裏,給指定插件的頁面加上相應的程序集。post
六、檢測插件目錄,有改變就自動從新加載。ui
--------------------------------------------我是分割線--------------------------------------------url
先建立一個空的MVC4項目。spa
清理站點插件
新建一個 PluginMvc.Framework 類庫,並建立插件接口(IPlugin)。
定義程序集搜索目錄(臨時目錄)。
建立一個PluginLoader的靜態類,作爲插件的加載器,並設置好插件目錄,臨時目錄。
臨時目錄就是以前在 Web.Config 中設置的程序集搜索目錄。
插件目錄就是存放插件的目錄。
namespace PluginMvc.Framework { using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Web.Hosting; /// <summary> /// 插件加載器。 /// </summary> public static class PluginLoader { /// <summary> /// 插件目錄。 /// </summary> private static readonly DirectoryInfo PluginFolder; /// <summary> /// 插件臨時目錄。 /// </summary> private static readonly DirectoryInfo TempPluginFolder; /// <summary> /// 初始化。 /// </summary> static PluginLoader() { PluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/Plugins")); TempPluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/App_Data/Dependencies")); } /// <summary> /// 加載插件。 /// </summary> public static IEnumerable<PluginDescriptor> Load() { List<PluginDescriptor> plugins = new List<PluginDescriptor>(); return plugins; } } }
將程序集複製到臨時目錄。
一、先刪除臨時目錄中的全部文件。
二、在把插件目錄中的程序集複製到臨時目錄裏。
/// <summary> /// 程序集複製到臨時目錄。 /// </summary> private static void FileCopyTo() { Directory.CreateDirectory(PluginFolder.FullName); Directory.CreateDirectory(TempPluginFolder.FullName); //清理臨時文件。 foreach (var file in TempPluginFolder.GetFiles("*.dll", SearchOption.AllDirectories)) { try { file.Delete(); } catch (Exception) { } } //複製插件進臨時文件夾。 foreach (var plugin in PluginFolder.GetFiles("*.dll", SearchOption.AllDirectories)) { try { var di = Directory.CreateDirectory(TempPluginFolder.FullName); File.Copy(plugin.FullName, Path.Combine(di.FullName, plugin.Name), true); } catch (Exception) { } } }
加載程序集。
一、先獲取系統自動加載的程序集(即:bin 目錄下的),經過反射得到其中的插件信息(程序集、插件接口的實現,對象類型,控制器類型等)。
二、使用 Assembly.LoadFile(fileName);方法,加載插件目錄下的全部程序集。
/// <summary> /// 加載插件。 /// </summary> public static IEnumerable<PluginDescriptor> Load() { List<PluginDescriptor> plugins = new List<PluginDescriptor>(); //程序集複製到臨時目錄。 FileCopyTo(); IEnumerable<Assembly> assemblies = null; //加載 bin 目錄下的全部程序集。 assemblies = AppDomain.CurrentDomain.GetAssemblies(); plugins.AddRange(GetAssemblies(assemblies)); //加載臨時目錄下的全部程序集。 assemblies = TempPluginFolder.GetFiles("*.dll", SearchOption.AllDirectories).Select(x => Assembly.LoadFile(x.FullName)); plugins.AddRange(GetAssemblies(assemblies)); return plugins; }
建立一個插件描述類,來保存插件的信息。
從程序集中反射得到插件的各類信息,並保存在插件描述中,如:插件接口的實現,控制器的類型等。
遍歷傳入的程序集集合,查找出全部實現了 IPlugin 接口的程序集,並把相關的全部信息保存到 PluginDescriptor 實體裏,返回全部該實體的列表。
/// <summary> /// 根據程序集列表得到該列表下的全部插件信息。 /// </summary> /// <param name="assemblies">程序集列表</param> /// <returns>插件信息集合。</returns> private static IEnumerable<PluginDescriptor> GetAssemblies(IEnumerable<Assembly> assemblies) { IList<PluginDescriptor> plugins = new List<PluginDescriptor>(); foreach (var assembly in assemblies) { var pluginTypes = assembly.GetTypes().Where(type => type.GetInterface(typeof(IPlugin).Name) != null && type.IsClass && !type.IsAbstract); foreach (var pluginType in pluginTypes) { var plugin = GetPluginInstance(pluginType, assembly); if (plugin != null) { plugins.Add(plugin); } } } return plugins; }
/// <summary> /// 得到插件信息。 /// </summary> /// <param name="pluginType"></param> /// <param name="assembly"></param> /// <returns></returns> private static PluginDescriptor GetPluginInstance(Type pluginType, Assembly assembly) { if (pluginType != null) { var plugin = (IPlugin)Activator.CreateInstance(pluginType); if (plugin != null) { return new PluginDescriptor(plugin, assembly, assembly.GetTypes()); } } return null; }
建立一個PluginManager類,可對全部插件進行初始化、卸載與獲取。
namespace PluginMvc.Framework { using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Web.Hosting; /// <summary> /// 插件管理器。 /// </summary> public static class PluginManager { /// <summary> /// 插件字典。 /// </summary> private readonly static IDictionary<string, PluginDescriptor> _plugins = new Dictionary<string, PluginDescriptor>(); /// <summary> /// 初始化。 /// </summary> public static void Initialize() { //遍歷全部插件描述。 foreach (var plugin in PluginLoader.Load()) { //卸載插件。 Unload(plugin); //初始化插件。 Initialize(plugin); } } /// <summary> /// 初始化插件。 /// </summary> /// <param name="pluginDescriptor">插件描述</param> private static void Initialize(PluginDescriptor pluginDescriptor) { //使用插件名稱作爲字典 KEY。 string key = pluginDescriptor.Plugin.Name; //不存在時才進行初始化。 if (!_plugins.ContainsKey(key)) { //初始化。 pluginDescriptor.Plugin.Initialize(); //增長到字典。 _plugins.Add(key, pluginDescriptor); } } /// <summary> /// 卸載。 /// </summary> public static void Unload() { //卸載全部插件。 foreach (var plugin in PluginLoader.Load()) { plugin.Plugin.Unload(); } //清空插件字典中的全部信息。 _plugins.Clear(); } /// <summary> /// 卸載。 /// </summary> public static void Unload(PluginDescriptor pluginDescriptor) { pluginDescriptor.Plugin.Unload(); _plugins.Remove(pluginDescriptor.Plugin.ToString()); } /// <summary> /// 得到當前系統全部插件描述。 /// </summary> /// <returns></returns> public static IEnumerable<PluginDescriptor> GetPlugins() { return _plugins.Select(m => m.Value).ToList(); } /// <summary> /// 根據插件名稱得到插件描述。 /// </summary> /// <param name="name">插件名稱。</param> /// <returns>插件描述。</returns> public static PluginDescriptor GetPlugin(string name) { return GetPlugins().SingleOrDefault(plugin => plugin.Plugin.Name == name); } } }
建立一個插件控制器工廠,來得到插件程序集中的控制器類型。
對 RazorViewEngine 的 FindPartialView 方法與 FindView 方法,根據插件來把該插件相關的程序集增長到 Razor 模板的編譯項裏。
關鍵代碼:
/// <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); } }; }
到如今,該方法已經初步完成,如今就是把整個插件丟到插件目錄下,重啓就能加載了!
如今,就給它加上自動檢測功能,FileSystemWatcher 類,設置當程序集發生修改、建立、刪除和重命名時,自動從新加載插件。
namespace PluginMvc.Framework { using System.IO; using System.Web.Hosting; /// <summary> /// 插件檢測器。 /// </summary> public static class PluginWatcher { /// <summary> /// 是否啓用。 /// </summary> private static bool _enable = false; /// <summary> /// 偵聽文件系統。 /// </summary> private static readonly FileSystemWatcher _fileSystemWatcher = new FileSystemWatcher(); static PluginWatcher() { _fileSystemWatcher.Path = HostingEnvironment.MapPath("~/Plugins"); _fileSystemWatcher.Filter = "*.dll"; _fileSystemWatcher.Changed += _fileSystemWatcher_Changed; _fileSystemWatcher.Created += _fileSystemWatcher_Created; _fileSystemWatcher.Deleted += _fileSystemWatcher_Deleted; _fileSystemWatcher.Renamed += _fileSystemWatcher_Renamed; _fileSystemWatcher.IncludeSubdirectories = true; Enable = false; } /// <summary> /// 是否啓用。 /// </summary> public static bool Enable { get { return _enable; } set { _enable = value; _fileSystemWatcher.EnableRaisingEvents = _enable; } } /// <summary> /// 啓動。 /// </summary> public static void Start() { Enable = true; } /// <summary> /// 中止。 /// </summary> public static void Stop() { Enable = false; } private static void _fileSystemWatcher_Changed(object sender, FileSystemEventArgs e) { ResetPlugin(); } private static void _fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e) { ResetPlugin(); } private static void _fileSystemWatcher_Created(object sender, FileSystemEventArgs e) { ResetPlugin(); } private static void _fileSystemWatcher_Renamed(object sender, RenamedEventArgs e) { ResetPlugin(); } /// <summary> /// 重置插件。 /// </summary> private static void ResetPlugin() { PluginManager.Unload(); PluginManager.Initialize(); } } }
把該方法進行註冊:
又或者能夠使用 System.Web.PreApplicationStartMethod 方法來啓動(推薦)。
[assembly: System.Web.PreApplicationStartMethod(typeof(PluginMvc.Framework.Bootstrapper), "Initialize")] namespace PluginMvc.Framework { using System.Web.Mvc; using PluginMvc.Framework; using PluginMvc.Framework.Mvc; /// <summary> /// 引導程序。 /// </summary> public static class Bootstrapper { /// <summary> /// 初始化。 /// </summary> public static void Initialize() { //註冊插件控制器工廠。 ControllerBuilder.Current.SetControllerFactory(new PluginControllerFactory()); //註冊插件模板引擎。 ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new PluginRazorViewEngine()); //初始化插件。 PluginManager.Initialize(); //啓動插件檢測器。 PluginWatcher.Start(); } } }
到這裏,框架部分已經完成了!下面說下插件的開發。
一、建立一個空的 ASP.NET MVC 4 項目,並清理好。
二、定義一個實現 IPlugin 接口的類。
三、完成一個簡單的頁面顯示功能。
將該插件站點發布。
將已發佈的插件包含目錄一塊兒複製到站點的插件目錄下便可。
完成了,如今不但能夠把插件複製到插件目錄就立刻能使用,要調試什麼的,也能夠直接啓動插件 WEB 項目了,具體的完善就很少說了!
不過,目前還有個小BUG,若是目錄下沒有任何插件的時候,插件檢測將不會啓動><。
注意!Views目錄下必需要存在 Web.Config 文件,.NET 會根據該文件自動配置 cshtml 頁面的基類等信息,假如沒有該文件,編譯頁面時,會出現找不到基類錯誤。
源碼: