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类,可对所有插件进行初始化、卸载与获取。
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 插件化架构简单实现-实例篇
ASP.NET MVC 4 插件化架构简单实现-实例篇的更多相关文章
- ASP.NET MVC 4 插件化架构简单实现-思路篇
用过和做过插件的都会了解插件的好处,园子里也有很多和讨论,但大都只些简单的加载程序集什么的,这里主要讨论的就是使用 ASP.NET MVC 4 来实现每个插件都可以完全从主站点剥离出来,即使只是一个插 ...
- MVC 4 插件化架构简单实现实例篇
ASP.NET MVC 4 插件化架构简单实现-实例篇 先回顾一下上篇决定的做法: 1.定义程序集搜索目录(临时目录). 2.将要使用的各种程序集(插件)复制到该目录. 3.加载临时目录中的程序集 ...
- MVC 4 插件化架构简单实现
转ASP.NET MVC 4 插件化架构简单实现-思路篇 用过和做过插件的都会了解插件的好处,园子里也有很多和讨论,但大都只些简单的加载程序集什么的,这里主要讨论的就是使用 ASP.NET MVC ...
- ASP.NET MVC5 插件化机制简单实现
一.前言 nopCommerce的插件机制的核心是使用BuildManager.AddReferencedAssembly将使用Assembly.Load加载的插件程序集添加到应用程序域的引用中.具体 ...
- 基于.NET MVC的高性能IOC插件化架构(一)
最近闲下来,整理了下最近写的代码,先写写架构,后面再分享几个我自己写的插件 最近经过反复对比,IOC框架选择了Autofac,原因很简单,性能出众,这篇博文是我的各大IOC框架的性能测试:http:/ ...
- 基于.NET MVC的高性能IOC插件化架构
基于.NET MVC的高性能IOC插件化架构 最近闲下来,整理了下最近写的代码,先写写架构,后面再分享几个我自己写的插件 最近经过反复对比,IOC框架选择了Autofac,原因很简单,性能出众,这篇博 ...
- Asp.Net MVC+BootStrap+EF6.0实现简单的用户角色权限管理
这是本人第一次写,写的不好的地方还忘包含.写这个的主要原因是想通过这个来学习下EF的CodeFirst模式,本来也想用AngularJs来玩玩的,但是自己只会普通的绑定,对指令这些不是很熟悉,所以就基 ...
- Asp.Net MVC页面静态化功能实现二:用递归算法来实现
上一篇提到采用IHttpModule来实现当用户访问网站的时候,通过重新定义Response.Filter来实现将返回给客户端的html代码保存,以便用户下一次访问是直接访问静态页面. Asp.Net ...
- Asp.Net MVC页面静态化功能实现一:利用IHttpModule,摒弃ResultFilter
上一篇有提到利用IHttpModule和ResultFilter实现页面静态化功能.后来经过一些改动,将ResultFilter中要实现的功能全部转移到IHttpModule中来实现 Asp.Net ...
随机推荐
- JAVA 模糊查询方法
当我们需要开发一个方法用来查询数据库的时候,往往会遇到这样一个问题:就是不知道用户到底会输入什么条件,那么怎么样处理sql语句才能让我们开发的方法不管接受到什么样的条件都可以正常工作呢?这时where ...
- IOS开发: 为UIImageView添加点击事件
转载于:http://www.pocketdigi.com/20140218/1276.html UIImageView并不像UIButton一样,点点鼠标就可以关联点击事件,也不像Android里有 ...
- [Bootstrap]组件(三)
输入框组 添加额外元素.input-group-addon 外包元素.input-group>input-group-addon+form-control <div class=" ...
- 7款震撼人心的HTML5文字特效
1.CSS3五彩文字特效 文字带阴影效果 这是一款非常具有卡通形象的CSS3五彩文字特效,虽然没有迷人的动画效果,但是五彩缤纷的文字展现在屏幕上也是非常酷的,再加上每一个文字都有不同角度的阴影效果,加 ...
- Codevs 2611 观光旅游
时间限制: 1 s 空间限制: 128000 KB 题目等级:钻石 题目描述 Description 某旅游区里面有N个景点.两个景点之间可能直接有道路相连,用a[i][j]表示它的长度,否则它 ...
- H.264的一些资料整理
本文转载自 http://blog.csdn.net/ljzcom/article/details/7258978, 如有需要,请移步查看. Technorati 标签: H.264 资料整理 --- ...
- UILabel常用属性
[super viewDidLoad]; // 实例化UILabel并指定其边框 UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(0 ...
- HTML5-WebWorker
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- Android开发之计算器(一)界面设计
计算器开发主要涉及到LinearLayout布局.EditText.Button的使用.为android入门基础内容. 打开android studio选择创建一个新的工程,应用程序的名称为Calcu ...
- [DevExpress]ChartControl之柱状图示例
关键代码: using System; using System.Data; using System.Windows.Forms; using CSharpUtilHelpV2; using Dev ...