ASP.NET MVC 4 插件化架构简单实现-实例篇
先回顾一下上篇决定的做法:
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类,可对所有插件进行初始化、卸载与获取。
创建一个插件控制器工厂,来获得插件程序集中的控制器类型。
对 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 插件化架构简单实现-实例篇
设计模式之结构型模式
结构型模式涉及到如何组合类和对象以获得更大的结构。
结构型模式包括:适配器模式,桥接模式,装饰者模式,组合模式,外观模式,享元模式,外观模式
1.适配器模式
适配器:将一个类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作的那些类能一起工作。比如一般购物网站都能将自己喜欢的商品加入favorite,而亚马逊则是加入wishlist。我们想要一个统一的接口对所有网站都提供favorite接口,这时就需要用到适配器了。
2.桥接模式
Bridge模式用来将抽象部分与它的实现部分分离,使它们都可以独立地变化。
这个有点类似于外包公司,这个外包公司给我们提供了一个服务列表,比如服务A是给我们提供B公司的C产品,这时外包公司的服务列表就是抽象部分,B公司则负责具体的生产,外包公司不关心B公司的具体生产,但是他可以通过对B公司的产品进行定制,生成不同的服务,B公司也不关心外包公司提供什么服务,而只是关心有哪些基本的产品需要生产。
3.组合模式
组合模式使得用户对单个对象和组合对象的使用具有一致性。
比如一副图像,它可能由很多图元和子图像组合而成,其中子图像可能又由图元和子图像组合而成,就像一棵多叉树。
4.装饰者模式
装饰者模式:动态地给一个对象添加一些额外的职责。注意是给对象而不是类添加职责。
装饰者其实就是把对象包装了一下,把对对象额外进行的操作封装到了一个装饰类中。
5.外观模式
Facade模式为子系统中的一组接口提供一个一致的界面。
就好象我们画一个三角形,我们要依次画三角形的三条边,但是我们实际上并不关心这个三角形是怎样画出来的,我们只想要有一个画三角形的接口Draw,我们只要调用这个接口,就能画出这个三角形就行了。
6.代理模式
代理模式为其他对象提供一种代理以控制对这个对象的访问。
下面是一些需要使用代理的情形:
我们在对一个对象下达指令之前必须要先创建和初始化这个对象,对于一个比较大的对象,我们更倾向于在需要使用它的时候再去创建和初始化这个对象,我们将这个创建,初始化和使用对象的一系列操作都封装在了代理中。
我们需要对要访问的对象提供某种访问保护,这时我们将对象的权限验证和使用对象的一系列操作都封装在代理中。