ASP.NET MVC 4 插件化架構簡單實現-實例篇

先回顧一下上篇決定的作法:html

一、定義程序集搜索目錄(臨時目錄)。架構

二、將要使用的各類程序集(插件)複製到該目錄。app

三、加載臨時目錄中的程序集。框架

四、定義模板引擎的搜索路徑。ide

五、在模板引擎的查找頁面方法裏,給指定插件的頁面加上相應的程序集。post

六、檢測插件目錄,有改變就自動從新加載。ui

--------------------------------------------我是分割線--------------------------------------------url

先建立一個空的MVC4項目。spa

清理站點插件

新建一個 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;
        }


    }
}

 

 

將程序集複製到臨時目錄。

 

一、先刪除臨時目錄中的全部文件。

二、在把插件目錄中的程序集複製到臨時目錄裏。

 

        /// <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)
                {

                }
            }
        }

 

 

 

加載程序集。

 

一、先獲取系統自動加載的程序集(即:bin 目錄下的),經過反射得到其中的插件信息(程序集、插件接口的實現,對象類型,控制器類型等)。

二、使用 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();
        }
    }
}

 

到這裏,框架部分已經完成了!下面說下插件的開發。

 一、建立一個空的 ASP.NET MVC 4 項目,並清理好。

 

 二、定義一個實現 IPlugin 接口的類。

 三、完成一個簡單的頁面顯示功能。

 

將該插件站點發布。

 

將已發佈的插件包含目錄一塊兒複製到站點的插件目錄下便可。

 

完成了,如今不但能夠把插件複製到插件目錄就立刻能使用,要調試什麼的,也能夠直接啓動插件 WEB 項目了,具體的完善就很少說了!

不過,目前還有個小BUG,若是目錄下沒有任何插件的時候,插件檢測將不會啓動><。

注意!Views目錄下必需要存在 Web.Config 文件,.NET 會根據該文件自動配置 cshtml 頁面的基類等信息,假如沒有該文件,編譯頁面時,會出現找不到基類錯誤。

源碼:

點擊下載

 

ASP.NET MVC 4 插件化架構簡單實現-思路篇

 

ASP.NET MVC 4 插件化架構簡單實現-實例篇

相關文章
相關標籤/搜索