zoukankan      html  css  js  c++  java
  • 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);
            }
        }
    }
    View Code

    创建一个插件控制器工厂,来获得插件程序集中的控制器类型。

    对 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 插件化架构简单实现-实例篇

  • 相关阅读:
    递归
    书评:《C程序设计语言》
    下一代互联网,今日揭开面纱:IPv6真的要来了
    庆祝Alan Mathison Turing(艾伦·图灵)诞辰100周年!
    svn常用命令行和批处理
    ORACLE 9i数据导入到ORACLE 10G中文出现的乱码问题
    Oracle 11G 的客户端,不再支持连接到ORACLE 8I
    DB2 一个汉字的Byte数,太操蛋了
    关于Windows 7 64位下Visual Studio 2010 开发的Asp.net程序连接Oracle 的出现的问题
    Web Frame 跨域调用Session 丢失问题
  • 原文地址:https://www.cnblogs.com/cjnmy36723/p/3405272.html
Copyright © 2011-2022 走看看