ASP.NET MVC 4 插件化架构简单实现-实例篇

先回顾一下上篇决定的做法:

1、定义程序集搜索目录(临时目录)。

2、将要使用的各种程序集(插件)复制到该目录。

3、加载临时目录中的程序集。

4、定义模板引擎的搜索路径。

5、在模板引擎的查找页面方法里,给指定插件的页面加上相应的程序集。

6、检测插件目录,有改变就自动重新加载。

--------------------------------------------我是分割线--------------------------------------------

先创建一个空的MVC4项目。

ASP.NET MVC 4 插件化架构简单实现-实例篇

清理站点

ASP.NET MVC 4 插件化架构简单实现-实例篇

新建一个 PluginMvc.Framework 类库,并创建插件接口(IPlugin)。

ASP.NET MVC 4 插件化架构简单实现-实例篇

定义程序集搜索目录(临时目录)。

ASP.NET MVC 4 插件化架构简单实现-实例篇

创建一个PluginLoader的静态类,做为插件的加载器,并设置好插件目录,临时目录。

临时目录就是之前在 Web.Config 中设置的程序集搜索目录。

插件目录就是存放插件的目录。

[csharp] view plain copy
 
 print?
  1. namespace PluginMvc.Framework  
  2. {  
  3.     using System;  
  4.     using System.Collections.Generic;  
  5.     using System.IO;  
  6.     using System.Linq;  
  7.     using System.Reflection;  
  8.     using System.Web.Hosting;  
  9.   
  10.     /// <summary>  
  11.     /// 插件加载器。  
  12.     /// </summary>  
  13.     public static class PluginLoader  
  14.     {  
  15.         /// <summary>  
  16.         /// 插件目录。  
  17.         /// </summary>  
  18.         private static readonly DirectoryInfo PluginFolder;  
  19.   
  20.         /// <summary>  
  21.         /// 插件临时目录。  
  22.         /// </summary>  
  23.         private static readonly DirectoryInfo TempPluginFolder;  
  24.   
  25.         /// <summary>  
  26.         /// 初始化。  
  27.         /// </summary>  
  28.         static PluginLoader()  
  29.         {  
  30.             PluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/Plugins"));  
  31.             TempPluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/App_Data/Dependencies"));  
  32.         }  
  33.   
  34.         /// <summary>  
  35.         /// 加载插件。  
  36.         /// </summary>  
  37.         public static IEnumerable<PluginDescriptor> Load()  
  38.         {  
  39.             List<PluginDescriptor> plugins = new List<PluginDescriptor>();  
  40.   
  41.             return plugins;  
  42.         }  
  43.   
  44.   
  45.     }  
  46. }  

将程序集复制到临时目录。

1、先删除临时目录中的所有文件。

2、在把插件目录中的程序集复制到临时目录里。

[csharp] view plain copy
 
 print?
  1. <span style="white-space:pre">    </span>/// <summary>  
  2.         /// 程序集复制到临时目录。  
  3.         /// </summary>  
  4.         private static void FileCopyTo()  
  5.         {  
  6.             Directory.CreateDirectory(PluginFolder.FullName);  
  7.             Directory.CreateDirectory(TempPluginFolder.FullName);  
  8.   
  9.             //清理临时文件。  
  10.             foreach (var file in TempPluginFolder.GetFiles("*.dll", SearchOption.AllDirectories))  
  11.             {  
  12.                 try  
  13.                 {  
  14.                     file.Delete();  
  15.                 }  
  16.                 catch (Exception)  
  17.                 {  
  18.   
  19.                 }  
  20.   
  21.             }  
  22.   
  23.             //复制插件进临时文件夹。  
  24.             foreach (var plugin in PluginFolder.GetFiles("*.dll", SearchOption.AllDirectories))  
  25.             {  
  26.                 try  
  27.                 {  
  28.                     var di = Directory.CreateDirectory(TempPluginFolder.FullName);  
  29.                     File.Copy(plugin.FullName, Path.Combine(di.FullName, plugin.Name), true);  
  30.                 }  
  31.                 catch (Exception)  
  32.                 {  
  33.   
  34.                 }  
  35.             }  
  36.         }  

加载程序集。

1、先获取系统自动加载的程序集(即:bin 目录下的),通过反射获得其中的插件信息(程序集、插件接口的实现,对象类型,控制器类型等)。

2、使用 Assembly.LoadFile(fileName);方法,加载插件目录下的所有程序集。

[csharp] view plain copy
 
 print?
  1. <span style="white-space:pre">    </span>/// <summary>  
  2.         /// 加载插件。  
  3.         /// </summary>  
  4.         public static IEnumerable<PluginDescriptor> Load()  
  5.         {  
  6.             List<PluginDescriptor> plugins = new List<PluginDescriptor>();  
  7.   
  8.             //程序集复制到临时目录。  
  9.             FileCopyTo();  
  10.   
  11.             IEnumerable<Assembly> assemblies = null;  
  12.   
  13.             //加载 bin 目录下的所有程序集。  
  14.             assemblies = AppDomain.CurrentDomain.GetAssemblies();  
  15.   
  16.             plugins.AddRange(GetAssemblies(assemblies));  
  17.   
  18.             //加载临时目录下的所有程序集。  
  19.             assemblies = TempPluginFolder.GetFiles("*.dll", SearchOption.AllDirectories).Select(x => Assembly.LoadFile(x.FullName));  
  20.   
  21.             plugins.AddRange(GetAssemblies(assemblies));  
  22.   
  23.             return plugins;  
  24.         }  

创建一个插件描述类,来保存插件的信息。

ASP.NET MVC 4 插件化架构简单实现-实例篇

从程序集中反射获得插件的各种信息,并保存在插件描述中,如:插件接口的实现,控制器的类型等。

遍历传入的程序集集合,查找出所有实现了 IPlugin 接口的程序集,并把相关的所有信息保存到 PluginDescriptor 实体里,返回所有该实体的列表。

[csharp] view plain copy
 
 print?
  1. <span style="white-space:pre">    </span>/// <summary>  
  2.         /// 根据程序集列表获得该列表下的所有插件信息。  
  3.         /// </summary>  
  4.         /// <param name="assemblies">程序集列表</param>  
  5.         /// <returns>插件信息集合。</returns>  
  6.         private static IEnumerable<PluginDescriptor> GetAssemblies(IEnumerable<Assembly> assemblies)  
  7.         {  
  8.             IList<PluginDescriptor> plugins = new List<PluginDescriptor>();  
  9.   
  10.             foreach (var assembly in assemblies)  
  11.             {  
  12.                 var pluginTypes = assembly.GetTypes().Where(type => type.GetInterface(typeof(IPlugin).Name) != null && type.IsClass && !type.IsAbstract);  
  13.   
  14.                 foreach (var pluginType in pluginTypes)  
  15.                 {  
  16.                     var plugin = GetPluginInstance(pluginType, assembly);  
  17.   
  18.                     if (plugin != null)  
  19.                     {  
  20.                         plugins.Add(plugin);  
  21.                     }  
  22.                 }  
  23.             }  
  24.   
  25.             return plugins;  
  26.         }  

[csharp] view plain copy
 
 print?
  1. <span style="white-space:pre">    </span>/// <summary>  
  2.         /// 获得插件信息。  
  3.         /// </summary>  
  4.         /// <param name="pluginType"></param>  
  5.         /// <param name="assembly"></param>  
  6.         /// <returns></returns>  
  7.         private static PluginDescriptor GetPluginInstance(Type pluginType, Assembly assembly)  
  8.         {  
  9.             if (pluginType != null)  
  10.             {  
  11.                 var plugin = (IPlugin)Activator.CreateInstance(pluginType);  
  12.   
  13.                 if (plugin != null)  
  14.                 {  
  15.                     return new PluginDescriptor(plugin, assembly, assembly.GetTypes());  
  16.                 }  
  17.             }  
  18.   
  19.             return null;  
  20.         }  

创建一个PluginManager类,可对所有插件进行初始化、卸载与获取。
[csharp] view plain copy
 
 print?
  1. namespace PluginMvc.Framework  
  2. {  
  3.     using System;  
  4.     using System.Collections.Generic;  
  5.     using System.IO;  
  6.     using System.Linq;  
  7.     using System.Reflection;  
  8.     using System.Web.Hosting;  
  9.   
  10.     /// <summary>  
  11.     /// 插件管理器。  
  12.     /// </summary>  
  13.     public static class PluginManager  
  14.     {  
  15.         /// <summary>  
  16.         /// 插件字典。  
  17.         /// </summary>  
  18.         private readonly static IDictionary<string, PluginDescriptor> _plugins = new Dictionary<string, PluginDescriptor>();  
  19.   
  20.         /// <summary>  
  21.         /// 初始化。  
  22.         /// </summary>  
  23.         public static void Initialize()  
  24.         {  
  25.             //遍历所有插件描述。  
  26.             foreach (var plugin in PluginLoader.Load())  
  27.             {  
  28.                 //卸载插件。  
  29.                 Unload(plugin);  
  30.                 //初始化插件。  
  31.                 Initialize(plugin);  
  32.             }  
  33.         }  
  34.   
  35.         /// <summary>  
  36.         /// 初始化插件。  
  37.         /// </summary>  
  38.         /// <param name="pluginDescriptor">插件描述</param>  
  39.         private static void Initialize(PluginDescriptor pluginDescriptor)  
  40.         {  
  41.             //使用插件名称做为字典 KEY。  
  42.             string key = pluginDescriptor.Plugin.Name;  
  43.   
  44.             //不存在时才进行初始化。  
  45.             if (!_plugins.ContainsKey(key))  
  46.             {  
  47.                 //初始化。  
  48.                 pluginDescriptor.Plugin.Initialize();  
  49.   
  50.                 //增加到字典。  
  51.                 _plugins.Add(key, pluginDescriptor);  
  52.             }  
  53.         }  
  54.   
  55.         /// <summary>  
  56.         /// 卸载。  
  57.         /// </summary>  
  58.         public static void Unload()  
  59.         {  
  60.             //卸载所有插件。  
  61.             foreach (var plugin in PluginLoader.Load())  
  62.             {  
  63.                 plugin.Plugin.Unload();  
  64.             }  
  65.   
  66.             //清空插件字典中的所有信息。  
  67.             _plugins.Clear();  
  68.         }  
  69.   
  70.         /// <summary>  
  71.         /// 卸载。  
  72.         /// </summary>  
  73.         public static void Unload(PluginDescriptor pluginDescriptor)  
  74.         {  
  75.             pluginDescriptor.Plugin.Unload();  
  76.   
  77.             _plugins.Remove(pluginDescriptor.Plugin.ToString());  
  78.         }  
  79.   
  80.         /// <summary>  
  81.         /// 获得当前系统所有插件描述。  
  82.         /// </summary>  
  83.         /// <returns></returns>  
  84.         public static IEnumerable<PluginDescriptor> GetPlugins()  
  85.         {  
  86.             return _plugins.Select(m => m.Value).ToList();  
  87.         }  
  88.   
  89.         /// <summary>  
  90.         /// 根据插件名称获得插件描述。  
  91.         /// </summary>  
  92.         /// <param name="name">插件名称。</param>  
  93.         /// <returns>插件描述。</returns>  
  94.         public static PluginDescriptor GetPlugin(string name)  
  95.         {  
  96.             return GetPlugins().SingleOrDefault(plugin => plugin.Plugin.Name == name);  
  97.         }  
  98.     }  
  99. }  
创建一个插件控制器工厂,来获得插件程序集中的控制器类型。ASP.NET MVC 4 插件化架构简单实现-实例篇
对 RazorViewEngine 的 FindPartialView 方法与 FindView 方法,根据插件来把该插件相关的程序集增加到 Razor 模板的编译项里。

ASP.NET MVC 4 插件化架构简单实现-实例篇

关键代码:

[csharp] view plain copy
 
 print?
  1. <span style="white-space:pre">    </span>/// <summary>  
  2.         /// 给运行时编译的页面加了引用程序集。  
  3.         /// </summary>  
  4.         /// <param name="pluginName"></param>  
  5.         private void CodeGeneration(string pluginName)  
  6.         {  
  7.             RazorBuildProvider.CodeGenerationStarted += (object sender, EventArgs e) =>  
  8.             {  
  9.                 RazorBuildProvider provider = (RazorBuildProvider)sender;  
  10.   
  11.                 var plugin = PluginManager.GetPlugin(pluginName);  
  12.   
  13.                 if (plugin != null)  
  14.                 {  
  15.                     provider.AssemblyBuilder.AddAssemblyReference(plugin.Assembly);  
  16.                 }  
  17.             };  
  18.         }  

到现在,该方法已经初步完成,现在就是把整个插件丢到插件目录下,重启就能加载了!

现在,就给它加上自动检测功能,FileSystemWatcher 类,设置当程序集发生修改、创建、删除和重命名时,自动重新加载插件。

[csharp] view plain copy
 
 print?
  1. namespace PluginMvc.Framework  
  2. {  
  3.     using System.IO;  
  4.     using System.Web.Hosting;  
  5.   
  6.     /// <summary>  
  7.     /// 插件检测器。  
  8.     /// </summary>  
  9.     public static class PluginWatcher  
  10.     {  
  11.         /// <summary>  
  12.         /// 是否启用。  
  13.         /// </summary>  
  14.         private static bool _enable = false;  
  15.   
  16.         /// <summary>  
  17.         /// 侦听文件系统。  
  18.         /// </summary>  
  19.         private static readonly FileSystemWatcher _fileSystemWatcher = new FileSystemWatcher();  
  20.   
  21.         static PluginWatcher()  
  22.         {  
  23.             _fileSystemWatcher.Path = HostingEnvironment.MapPath("~/Plugins");  
  24.             _fileSystemWatcher.Filter = "*.dll";  
  25.   
  26.             _fileSystemWatcher.Changed += _fileSystemWatcher_Changed;  
  27.             _fileSystemWatcher.Created += _fileSystemWatcher_Created;  
  28.             _fileSystemWatcher.Deleted += _fileSystemWatcher_Deleted;  
  29.             _fileSystemWatcher.Renamed += _fileSystemWatcher_Renamed;  
  30.   
  31.             _fileSystemWatcher.IncludeSubdirectories = true;  
  32.   
  33.             Enable = false;  
  34.         }  
  35.   
  36.         /// <summary>  
  37.         /// 是否启用。  
  38.         /// </summary>  
  39.         public static bool Enable  
  40.         {  
  41.             get  
  42.             {  
  43.                 return _enable;  
  44.             }  
  45.             set  
  46.             {  
  47.                 _enable = value;  
  48.   
  49.                 _fileSystemWatcher.EnableRaisingEvents = _enable;  
  50.             }  
  51.         }  
  52.   
  53.         /// <summary>  
  54.         /// 启动。  
  55.         /// </summary>  
  56.         public static void Start()  
  57.         {  
  58.             Enable = true;  
  59.         }  
  60.   
  61.         /// <summary>  
  62.         /// 停止。  
  63.         /// </summary>  
  64.         public static void Stop()  
  65.         {  
  66.             Enable = false;  
  67.         }  
  68.   
  69.         private static void _fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)  
  70.         {  
  71.             ResetPlugin();  
  72.         }  
  73.   
  74.         private static void _fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)  
  75.         {  
  76.             ResetPlugin();  
  77.         }  
  78.   
  79.         private static void _fileSystemWatcher_Created(object sender, FileSystemEventArgs e)  
  80.         {  
  81.             ResetPlugin();  
  82.         }  
  83.   
  84.         private static void _fileSystemWatcher_Renamed(object sender, RenamedEventArgs e)  
  85.         {  
  86.             ResetPlugin();  
  87.         }  
  88.   
  89.         /// <summary>  
  90.         /// 重置插件。  
  91.         /// </summary>  
  92.         private static void ResetPlugin()  
  93.         {  
  94.             PluginManager.Unload();  
  95.             PluginManager.Initialize();  
  96.         }  
  97.     }  
  98. }  

把该方法进行注册:

ASP.NET MVC 4 插件化架构简单实现-实例篇

 又或者可以使用 System.Web.PreApplicationStartMethod 方法来启动(推荐)。

[csharp] view plain copy
 
 print?
  1. [assembly: System.Web.PreApplicationStartMethod(typeof(PluginMvc.Framework.Bootstrapper), "Initialize")]  
  2. namespace PluginMvc.Framework  
  3. {  
  4.     using System.Web.Mvc;  
  5.   
  6.     using PluginMvc.Framework;  
  7.     using PluginMvc.Framework.Mvc;  
  8.   
  9.     /// <summary>  
  10.     /// 引导程序。  
  11.     /// </summary>  
  12.     public static class Bootstrapper  
  13.     {  
  14.         /// <summary>  
  15.         /// 初始化。  
  16.         /// </summary>  
  17.         public static void Initialize()  
  18.         {  
  19.             //注册插件控制器工厂。  
  20.             ControllerBuilder.Current.SetControllerFactory(new PluginControllerFactory());  
  21.   
  22.             //注册插件模板引擎。  
  23.             ViewEngines.Engines.Clear();  
  24.             ViewEngines.Engines.Add(new PluginRazorViewEngine());  
  25.   
  26.             //初始化插件。  
  27.             PluginManager.Initialize();  
  28.   
  29.             //启动插件检测器。  
  30.             PluginWatcher.Start();  
  31.         }  
  32.     }  
  33. }  

到这里,框架部分已经完成了!下面说下插件的开发。

 1、创建一个空的 ASP.NET MVC 4 项目,并清理好。

ASP.NET MVC 4 插件化架构简单实现-实例篇

 2、定义一个实现 IPlugin 接口的类。

ASP.NET MVC 4 插件化架构简单实现-实例篇

3、完成一个简单的页面显示功能。

ASP.NET MVC 4 插件化架构简单实现-实例篇

将该插件站点发布。

ASP.NET MVC 4 插件化架构简单实现-实例篇

将已发布的插件包含目录一起复制到站点的插件目录下即可。

ASP.NET MVC 4 插件化架构简单实现-实例篇

完成了,现在不但可以把插件复制到插件目录就马上能使用,要调试什么的,也可以直接启动插件 WEB 项目了,具体的完善就不多说了!

不过,目前还有个小BUG,如果目录下没有任何插件的时候,插件检测将不会启动><。

注意!Views目录下必须要存在 Web.Config 文件,.NET 会根据该文件自动配置 cshtml 页面的基类等信息,假如没有该文件,编译页面时,会出现找不到基类错误。

源码:

点击下载
 
0