紙殼CMS的插件載入機制

来源:https://www.cnblogs.com/seriawei/archive/2018/08/26/9537867.html
-Advertisement-
Play Games

紙殼CMS是一個開源的可視化設計CMS,通過拖拽,線上編輯的方式來創建網站。紙殼CMS是基於插件化設計的,可以通過擴展插件來實現不同的功能。並且紙殼CMS的插件是相互獨立的,各插件的引用也相互獨立,即各插件都可引用各自需要的nuget包來達到目的。而不用把引用加到底層。 ...


紙殼CMS是一個開源的可視化設計CMS,通過拖拽,線上編輯的方式來創建網站。

GitHub

https://github.com/SeriaWei/ZKEACMS.Core

歡迎Star,Fork,發PR。:)

插件化設計

紙殼CMS是基於插件化設計的,可以通過擴展插件來實現不同的功能。如何通過插件來擴展,可以參考這篇文章:

http://www.zkea.net/codesnippet/detail/zkeacms-plugin-development.html

紙殼CMS的插件是相互獨立的,各插件的引用也相互獨立,即各插件都可引用各自需要的nuget包來達到目的。而不用把引用加到底層。

插件存放目錄

紙殼CMS的插件的存放目錄在開發環境和已發佈的程式中是不一樣的。在開發環境,插件和其它的項目統一放在src目錄下:

而發佈程式以後,插件會在wwwroot/Plugins目錄下:

所以,如果在開發過程中要使用插件目錄時,需要使用特定的方法來獲取真實的目錄,如:

PluginBase.GetPath<SectionPlug>()

 

相關代碼

有關插件用到的所有相關代碼,都在 EasyFrameWork/Mvc/Plugin 目錄下:

插件載入

紙殼CMS在程式啟動時載入所有啟用的插件Loader.cs:

public IEnumerable<IPluginStartup> LoadEnablePlugins(IServiceCollection serviceCollection)
{
    var start = DateTime.Now;
    Loaders.AddRange(GetPlugins().Where(m => m.Enable && m.ID.IsNotNullAndWhiteSpace()).Select(m =>
    {
        var loader = new AssemblyLoader();
        loader.CurrentPath = m.RelativePath;
        var assemblyPath = Path.Combine(m.RelativePath, (HostingEnvironment.IsDevelopment() ? Path.Combine(AltDevelopmentPath) : string.Empty), m.FileName);

        Console.WriteLine("Loading: {0}", m.Name);

        var assemblies = loader.LoadPlugin(assemblyPath);
        assemblies.Each(assembly =>
        {
            if (!LoadedAssemblies.ContainsKey(assembly.FullName))
            {
                LoadedAssemblies.Add(assembly.FullName, assembly);
            }
        });
        return loader;
    }));
    Console.WriteLine("All plugins are loaded. Elapsed: {0}ms", (DateTime.Now - start).Milliseconds);
    return serviceCollection.ConfigurePlugin().BuildServiceProvider().GetPlugins();
}

AssemblyLoader

AssemblyLoader是載入插件DLL的關鍵,紙殼CMS主要通過它來載入插件,並載入插件的相關依賴,並註冊插件。

namespace Easy.Mvc.Plugin
{
    public class AssemblyLoader
    {
        private const string ControllerTypeNameSuffix = "Controller";
        private static bool Resolving { get; set; }
        public AssemblyLoader()
        {
            DependencyAssemblies = new List<Assembly>();
        }
        public string CurrentPath { get; set; }
        public string AssemblyPath { get; set; }
        public Assembly CurrentAssembly { get; private set; }
        public List<Assembly> DependencyAssemblies { get; private set; }
        private TypeInfo PluginTypeInfo = typeof(IPluginStartup).GetTypeInfo();
        public IEnumerable<Assembly> LoadPlugin(string path)
        {
            if (CurrentAssembly == null)
            {
                AssemblyPath = path;
                
                CurrentAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
                ResolveDenpendency(CurrentAssembly);
                RegistAssembly(CurrentAssembly);
                yield return CurrentAssembly;
                foreach (var item in DependencyAssemblies)
                {
                    yield return item;
                }
            }
            else { throw new Exception("A loader just can load one assembly."); }
        }

        private void ResolveDenpendency(Assembly assembly)
        {
            string currentName = assembly.GetName().Name;
            var dependencyCompilationLibrary = DependencyContext.Load(assembly)
                .CompileLibraries.Where(de => de.Name != currentName && !DependencyContext.Default.CompileLibraries.Any(m => m.Name == de.Name))
                .ToList();

            dependencyCompilationLibrary.Each(libaray =>
            {
                bool depLoaded = false;
                foreach (var item in libaray.Assemblies)
                {
                    var files = new DirectoryInfo(Path.GetDirectoryName(assembly.Location)).GetFiles(Path.GetFileName(item));
                    foreach (var file in files)
                    {
                        DependencyAssemblies.Add(AssemblyLoadContext.Default.LoadFromAssemblyPath(file.FullName));
                        depLoaded = true;
                        break;
                    }
                }
                if (!depLoaded)
                {
                    foreach (var item in libaray.ResolveReferencePaths())
                    {
                        if (File.Exists(item))
                        {
                            DependencyAssemblies.Add(AssemblyLoadContext.Default.LoadFromAssemblyPath(item));
                            break;
                        }
                    }
                }
            });


        }

        private void RegistAssembly(Assembly assembly)
        {
            List<TypeInfo> controllers = new List<TypeInfo>();
            PluginDescriptor plugin = null;
            foreach (var typeInfo in assembly.DefinedTypes)
            {
                if (typeInfo.IsAbstract || typeInfo.IsInterface) continue;

                if (IsController(typeInfo) && !controllers.Contains(typeInfo))
                {
                    controllers.Add(typeInfo);
                }
                else if (PluginTypeInfo.IsAssignableFrom(typeInfo))
                {
                    plugin = new PluginDescriptor();
                    plugin.PluginType = typeInfo.AsType();
                    plugin.Assembly = assembly;
                    plugin.CurrentPluginPath = CurrentPath;
                }
            }
            if (controllers.Count > 0 && !ActionDescriptorProvider.PluginControllers.ContainsKey(assembly.FullName))
            {
                ActionDescriptorProvider.PluginControllers.Add(assembly.FullName, controllers);
            }
            if (plugin != null)
            {
                PluginActivtor.LoadedPlugins.Add(plugin);
            }
        }
        protected bool IsController(TypeInfo typeInfo)
        {
            if (!typeInfo.IsClass)
            {
                return false;
            }

            if (typeInfo.IsAbstract)
            {
                return false;
            }


            if (!typeInfo.IsPublic)
            {
                return false;
            }

            if (typeInfo.ContainsGenericParameters)
            {
                return false;
            }

            if (typeInfo.IsDefined(typeof(NonControllerAttribute)))
            {
                return false;
            }

            if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) &&
                !typeInfo.IsDefined(typeof(ControllerAttribute)))
            {
                return false;
            }

            return true;
        }
    }
}

註冊插件時,需要將插件中的所有Controller分析出來,當用戶訪問到插件的對應Controller時,才可以實例化Controller並調用。

動態編譯插件視圖

ASP.NET MVC 的視圖(cshtml)是可以動態編譯的。但由於插件是動態載入的,編譯器並不知道編譯視圖所需要的引用在什麼地方,這會導致插件中的視圖編譯失敗。並且程式也需要告訴編譯器到哪裡去找這個視圖。PluginRazorViewEngineOptionsSetup.cs 便起到了這個作用。

由於開發環境的目錄不同,對以針對開發環境,需要一個視圖文件提供程式來解析視圖文件位置:

if (hostingEnvironment.IsDevelopment())
{
    options.FileProviders.Add(new DeveloperViewFileProvider(hostingEnvironment));
}

loader.GetPlugins().Where(m => m.Enable && m.ID.IsNotNullAndWhiteSpace()).Each(m =>
{
    var directory = new DirectoryInfo(m.RelativePath);
    if (hostingEnvironment.IsDevelopment())
    {
        options.ViewLocationFormats.Add($"{DeveloperViewFileProvider.ProjectRootPath}{directory.Name}" + "/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"{DeveloperViewFileProvider.ProjectRootPath}{directory.Name}" + "/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"{DeveloperViewFileProvider.ProjectRootPath}{directory.Name}" + "/Views/{0}" + RazorViewEngine.ViewExtension);
    }
    else
    {
        options.ViewLocationFormats.Add($"/wwwroot/{Loader.PluginFolder}/{directory.Name}" + "/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"/wwwroot/{Loader.PluginFolder}/{directory.Name}" + "/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"/wwwroot/{Loader.PluginFolder}/{directory.Name}" + "/Views/{0}" + RazorViewEngine.ViewExtension);
    }
});
options.ViewLocationFormats.Add("/Views/{0}" + RazorViewEngine.ViewExtension);

為瞭解決引用問題,需要把插件相關的所有引用都加入到編譯環境中:

loader.GetPluginAssemblies().Each(assembly =>
{
    var reference = MetadataReference.CreateFromFile(assembly.Location);
    options.AdditionalCompilationReferences.Add(reference);                
});

 


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

-Advertisement-
Play Games
更多相關文章
  • 一、生成器的定義 在函數中使用yield關鍵字,由函數返回的結果就是生成器。 1 def gen(): 2 print('gen') #函數內部的代碼不執行 3 yield 0 4 yield 1 5 yield 2 6 7 g = gen() 8 print(g) 9 print(next(g)) ...
  • 集群健康檢查 取得健康狀態 GET /_cat/health?v 返回: 健康狀態分類 green:索引的primary shard和replica shard都是active狀態的 yellow:索引的primary shard都是active狀態的,但是部分replica shard不是acti ...
  • 參考來源:https://www.cnblogs.com/liwenzhou/p/8747872.html ...
  • 小編也不知道大家能不能用的到,我只是把我學到的知識分享出來,有需要的可以看一下。python本身就是一個不斷更新改進的語言,不存在抄襲,有需要就可以拿過來用,在用的過程中,你發現可以用另外一種方法把它實現,就可以把代碼做進一步的優化,然後分享出來,這樣python會變的越來越實用。今天心情不好,分享 ...
  • 前言 編程其實就是寫代碼,而寫代碼目的就是實現業務,所以,語法和框架也是為了實現業務而存在的。因此,不管多麼高大上的目標,實質上都是業務。 所以,我認為不要把寫代碼上升到科學的高度。上升到藝術就可以了,因為藝術本身也沒有高度。。。。 軟體設計存在過度設計,語法和框架的理解,也存在過度理解。比如,反編 ...
  • 在第一篇Proto.Actor博文中,HelloWorld的第一行真正代碼是: var props = Actor.FromProducer(() => new HelloActor()); 這個返回的變數props就是一個Props的對象,它是負責創Actor實例,以及配置Actor實例,並... ...
  • 一,使用工具 ①Fiddler 摘自百度百科Fiddler簡介: Fiddler是一個http協議調試代理工具,它能夠記錄並檢查所有你的電腦和互聯網之間的http通訊,設置斷點,查看所有的“進出”Fiddler的數據(指cookie,html,js,css等文件,這些都可以讓你胡亂修改的意思)。 F ...
  • 在微服務中,數據最終一致性的一個解決方案是通過有狀態的Actor模型來達到,那什麼是Actor模型呢? Actor是並行的計算模型,包含狀態,行為,並且包含一個郵箱,來非同步處理消息。 關於Actor的介紹可參考: https://www.jianshu.com/p/449850aa8e82 http... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...