先回顧一下上篇決定的做法:
1、定義程序集搜索目錄(臨時目錄)。
2、將要使用的各種程序集(插件)復制到該目錄。
3、加載臨時目錄中的程序集。
4、定義模板引擎的搜索路徑。
5、在模板引擎的查找頁面方法里,給指定插件的頁面加上相應的程序集。
6、檢測插件目錄,有改變就自動重新加載。
--------------------------------------------我是分割線--------------------------------------------
先創建一個空的MVC4項目。
清理站點
新建一個 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; } } }
將程序集復制到臨時目錄。
1、先刪除臨時目錄中的所有文件。
2、在把插件目錄中的程序集復制到臨時目錄里。
/// <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) { } } }
加載程序集。
1、先獲取系統自動加載的程序集(即:bin 目錄下的),通過反射獲得其中的插件信息(程序集、插件接口的實現,對象類型,控制器類型等)。
2、使用 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(); } } }
到這里,框架部分已經完成了!下面說下插件的開發。
1、創建一個空的 ASP.NET MVC 4 項目,並清理好。
2、定義一個實現 IPlugin 接口的類。
3、完成一個簡單的頁面顯示功能。
將該插件站點發布。
將已發布的插件包含目錄一起復制到站點的插件目錄下即可。
完成了,現在不但可以把插件復制到插件目錄就馬上能使用,要調試什么的,也可以直接啟動插件 WEB 項目了,具體的完善就不多說了!
不過,目前還有個小BUG,如果目錄下沒有任何插件的時候,插件檢測將不會啟動><。
注意!Views目錄下必須要存在 Web.Config 文件,.NET 會根據該文件自動配置 cshtml 頁面的基類等信息,假如沒有該文件,編譯頁面時,會出現找不到基類錯誤。
源碼:
ASP.NET MVC 4 插件化架構簡單實現-思路篇
ASP.NET MVC 4 插件化架構簡單實現-實例篇