使用asp.net mvc引擎開發插件系統

来源:http://www.cnblogs.com/94pm/archive/2017/05/07/6819812.html
-Advertisement-
Play Games

一、前言 我心中的插件系統應該是像Nop那樣(更牛逼的如Orchard,OSGI.NET),每個插件模塊不只是一堆實現了某個業務介面的dll,然後採用反射或IOC技術來調用,而是一個完整的mvc小應用,我可以在後臺控制插件的安裝和禁用,目錄結構如下: 生成後放在站點根目錄下的Plugins文件夾中, ...


一、前言

我心中的插件系統應該是像Nop那樣(更牛逼的如Orchard,OSGI.NET),每個插件模塊不只是一堆實現了某個業務介面的dll,然後採用反射或IOC技術來調用,而是一個完整的mvc小應用,我可以在後臺控制插件的安裝和禁用,目錄結構如下:

生成後放在站點根目錄下的Plugins文件夾中,每個插件有一個子文件夾

Plugins/Sms.AliYun/

Plugins/Sms.ManDao/

我是一個有強迫症的的懶人,我不想將生成的dll文件拷貝到bin目錄。

二、要解決的問題

1.asp.net引擎預設只會載入“bin”文件夾中的dll,而我們想要的插件文件則是分散在Plugins目錄下的各個子目錄中。

2.視圖中使用了模型時如何處理?預設情況下RazorViewEngine使用BuildManager將視圖編譯成動態程式集,然後使用Activator.CreateInstance實例化新編譯的對象,而使用插件dll時,當前的AppDomain不知道如何解析這種引用了模型的視圖,因為它不存在於“bin”或GAC中。更糟糕的是,不會收到任何錯誤消息,告訴您為什麼它不工作,或者問題在哪。相反,他會告訴你,從View目錄中找不到文件。

3.某個插件正掛在站點下運行著,直接覆蓋插件的dll,會告訴你當前dll正在使用,不能被覆蓋。

4.視圖文件不放站點的View目錄中,該如何載入。

三.Net 4.0讓這一切變成可能

Net4.0有一個新特性是在應用程式初始化之前執行代碼的能力(PreApplicationStartMethodAttribute),這個特性使得應用程式在Application_Star前可以做一些工作,例如我們可以在應用啟動之前告知我們的mvc插件系統的dll放在哪,做預載入處理等。關於.net的幾個新特性,有歪果仁寫得有博客來介紹,點我。,關於PreApplicationStartMethodAttribute,有博友已經寫過了,點我。 Abp的啟動模塊應該也是使用PreApplicationStartMethodAttribute這個特性原理來實現的,具體是不是這樣還沒看。

四、解決方案

1.修改主站點web.config目錄,讓運行時除了載入bin目錄中的文件,還可以從其它目錄載入

 <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="Plugins/temp/" />
    </assemblyBinding>
  </runtime>

2.開發一個簡易的插件管理類,這個類的作用就是在Application_Start之前就把Plugins各子目錄中的dll拷貝到第1步指定的文件夾中,為了讓demo儘可能簡單,沒有對重覆的dll進行檢測(比如插件中引用了ef程式集,主站點也引用了,在站點bin目錄中已經存在ef的dll了,就沒必要再把插件中的dll拷貝到上面設置的動態程式集目錄中)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Compilation;
using System.Web.Hosting;
[assembly: PreApplicationStartMethod(typeof(Plugins.Core.PreApplicationInit), "Initialize")]
namespace Plugins.Core
{
    public class PreApplicationInit
    {

        static PreApplicationInit()
        {
            PluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins"));
            ShadowCopyFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins/temp"));
        }

        /// <summary>
        /// 插件所在目錄信息
        /// </summary>
        private static readonly DirectoryInfo PluginFolder;

        /// <summary>
        /// 程式應行時指定的dll目錄
        /// </summary>
        private static readonly DirectoryInfo ShadowCopyFolder;

        public static void Initialize()
        {
            Directory.CreateDirectory(ShadowCopyFolder.FullName);
            //清空插件dll運行目錄中的文件
            foreach (var f in ShadowCopyFolder.GetFiles("*.dll", SearchOption.AllDirectories))
            {
                f.Delete();
            }
            foreach (var plug in PluginFolder.GetFiles("*.dll", SearchOption.AllDirectories).Where(i=>i.Directory.Parent.Name== "plugins"))
            {
                File.Copy(plug.FullName, Path.Combine(ShadowCopyFolder.FullName, plug.Name), true);
            }
            foreach (var a in
                ShadowCopyFolder
                .GetFiles("*.dll", SearchOption.AllDirectories)
                .Select(x => AssemblyName.GetAssemblyName(x.FullName))
                .Select(x => Assembly.Load(x.FullName)))
            {
                BuildManager.AddReferencedAssembly(a);
            }

        }
    }
}

 

3.如何讓View引擎找到我們的視圖呢?答案是重寫RazorViewEngine的方法,我採用了約定大於配置的方式(假設我們的插件項目命名空間為Plugins.Apps.Sms,那麼預設的控制器命名空間為Plugins.Apps.Sms.Controllers,插件生成後的文件夾必須為/Plugins/Plugins.Apps.Sms/),通過分析當前控制器就可以知道當前插件的View目錄位置

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.WebPages.Razor;

namespace Plugins.Web
{
    public class CustomerViewEngine : RazorViewEngine
    {

        /// <summary>
        /// 定義視圖頁所在地址。
        /// </summary>
        private string[] _viewLocationFormats = new[]
        {
            "~/Views/Parts/{0}.cshtml",
            "~/Plugins/{pluginFolder}/Views/{1}/{0}.cshtml",
            "~/Plugins/{pluginFolder}/Views/Shared/{0}.cshtml",
            "~/Views/{1}/{0}.cshtml",
            "~/Views/Shared/{0}.cshtml",
        };
        public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            string ns = controllerContext.Controller.GetType().Namespace;
            string controller = controllerContext.Controller.GetType().Name.Replace("Controller", "");
            //說明是插件中的控制器,View目錄需要單獨處理
            if (ns.ToLower().Contains("plugins"))
            {
                var pluginsFolder = ns.ToLower().Replace(".controllers", "");
                ViewLocationFormats = ReplacePlaceholder(pluginsFolder);
            }
            return base.FindView(controllerContext, viewName, masterName, useCache);
        }
        /// <summary>
        /// 替換pluginFolder占位符
        /// </summary>
        /// <param name="folderName"></param>
        private string[] ReplacePlaceholder(string folderName)
        {
            string[] tempArray = new string[_viewLocationFormats.Length];
            if (_viewLocationFormats != null)
            {
                for (int i = 0; i < _viewLocationFormats.Length; i++)
                {
                    tempArray[i] = _viewLocationFormats[i].Replace("{pluginFolder}", folderName);
                }
            }
            return tempArray;
        }
    }
}

然後在主站點的Global.asax中將Razor引擎指定為我們重寫過的

 

4.開始製作一個插件目錄,跟我們平時建立的MVC項目並沒有太大區別,只是發佈時需要做一些設置。

.生成路徑要按照第3條的約定來寫,不然會找不到視圖文件

 .View目錄下的web.config和.cshtml文件要複製到生成目錄(在文件中點右鍵)

3.設置引用項目中的生成屬性,主程式下麵已經有了的就把“複製到輸出目錄”設置為無,要不然拷貝到動態bin目錄時會出錯,可以對第2步中的那個類改造一下,加入文件比較功能,bin目錄中沒有的,才拷貝到動態bin目錄中。

 

4.生成後的目錄結構如下:

 

 5.跑一下,一切正常,插件中的控制器工作正常,視圖中引用了Model也沒問題

到此,一個插件系統的核心部分就算完成了,你可繼續進行擴展,增加插件的發現、安裝、卸載功能,這些相對於核心功能來說,都是小兒科。後續我會基於Abp框架出一個插件系統的文章,有興趣的把小板凳準備好,瓜子花生買上:)

五、源代碼

點我下載

 

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 背水一戰 Windows 10 之 控制項(ScrollViewer 特性): Chaining - 鎖鏈, Rail - 軌道, Inertia - 慣性, Snap - 對齊, Zoom - 縮放 ...
  • 根據一個給定經緯度的點和距離,搜索附近5公裡範圍的地點 –合理利用演算法 功能需求:需要查詢一個站點(已知該站點經緯度)5公裡範圍內的其它站點。 方 法 一 :對每條記錄,去進行遍歷,跟資料庫中的每一個點進行距離計算,當距離小於5公裡時,認為匹配(效率極其低下,耗時長)。 方 法 二 :先過濾出大概的 ...
  • "沒有時間了!" 本次學習mongoDB,參考瞭如下文章: Using MongoDB with ASP.NET Core – Part II (Implementation) 來自 MongoDB學習筆記(二) 通過samus驅動實現基本數據操作 來自 略去如何安裝和調試MongoBD,可參見上述... ...
  • 我遇到問題產生的原因:資料庫表的某個欄位為不能為空。在修改實體屬性的時候,實體對應的表中不能為空的欄位為null。 詳情: 資料庫: c #: 錯誤代碼: 正確代碼: ...
  • 最近由於工作需要,做一個C#的簡單程式。學習了一些基礎東西先記下來。 主要有: 1.生成初始框架 2.打亂順序 3.游戲部分,點擊按鈕後與空白部分交換的只是Text和Visible部分 ...
  • 前端應用程式 ASP.NET Zero包含可以作為您的公共網站或應用程式著陸頁的起點的前端頁面。首次運行項目時,您會看到主頁如下所示: 這裡有兩頁:主頁和關於。這些頁面的內容只是占位符和演示目的。您可以根據需要完全刪除內容並構建頁面。此外,你應該改變的標誌與貴公司的標誌。 請參閱 metronic前 ...
  • 後端應用程式 這是用戶名和密碼輸入的實際應用程式。您將主要在此應用程式上添加您的業務需求。 應用文件夾 後端應用程式預設內置在專用區域,名為“ App ”,但可以在創建解決方案時確定。因此,所有控制器,視圖和模型都位於 Areas / App文件夾下。此外,相關腳本和樣式文件位於wwwroot / ...
  • ASP.NET Zero--開髮指南(Lyhcee 譯) 01. 前期介紹 02. 前期要求 03. 解決方案結構(層) 04. 前端應用程式 05. 後端應用程式 06.WEB.HOST應用程式 07.Migration控制台應用程式 08. 基礎設施 09. 基於令牌的認證&SWAGGER UI ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...