您的位置:首页 > 编程语言 > ASP

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

2017-09-15 14:35 274 查看
原文出处:http://blog.csdn.net/hao_ds/article/details/42102969

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

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

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

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

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

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

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

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

先创建一个空的MVC4项目。



清理站点



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



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



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

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

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

[csharp] view
plain copy

print?

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、在把插件目录中的程序集复制到临时目录里。

[csharp] view
plain copy

print?

<span style="white-space:pre"> </span>/// <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);方法,加载插件目录下的所有程序集。

[csharp] view
plain copy

print?

<span style="white-space:pre"> </span>/// <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 实体里,返回所有该实体的列表。

[csharp] view
plain copy

print?

<span style="white-space:pre"> </span>/// <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;

}

[csharp] view
plain copy

print?

<span style="white-space:pre"> </span>/// <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类,可对所有插件进行初始化、卸载与获取。

[csharp] view
plain copy

print?

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 模板的编译项里。



关键代码:

[csharp] view
plain copy

print?

<span style="white-space:pre"> </span>/// <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 类,设置当程序集发生修改、创建、删除和重命名时,自动重新加载插件。

[csharp] view
plain copy

print?

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 方法来启动(推荐)。

[csharp] view
plain copy

print?

[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 页面的基类等信息,假如没有该文件,编译页面时,会出现找不到基类错误。

源码:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: