纸壳cms的插件加载机制(代码片段)

seriawei seriawei     2022-12-25     303

关键词:

纸壳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: 0ms", (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.ProjectRootPathdirectory.Name" + "/Views/1/0" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"DeveloperViewFileProvider.ProjectRootPathdirectory.Name" + "/Views/Shared/0" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"DeveloperViewFileProvider.ProjectRootPathdirectory.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);                
);

 

纸壳cms可视化建站系统搭建多语言网站(代码片段)

纸壳CMS是可视化建站系统,现已经从架构上支持多语言。但是多语言功能默认是没有开启的。您可以从设置中开启多语言,或者随时关闭它,您可以随时进行切换。开启多语言如果您没有在系统设置中看到多语言设置菜单,首先... 查看详情

用docker自动构建纸壳cms

纸壳CMS可以运行在Docker上,接下来看看如何自动构建纸壳CMS的DockerImage。我们希望的是在代码提交到GitHub以后,容器镜像服务可以自动构建DockerImage,构建好以后,就可以直接拿这个DockerImage来运行了。Dockerfile最重要的,就是Docke... 查看详情

045-利用反射机制,简单的实现php插件模式(代码片段)

<?php//利用反射机制,简单的实现PHP插件模式#假设,我们有一款开源产品,所有开发者都必须在我定制的需求之上,进行二次开发,#而开发完成后的新模块,就是一个不一样的新插件,可以放在特定的位置进行自动加载#这是... 查看详情

javagc垃圾回收机制g1cms(代码片段)

CMS(ConcurrentMark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参数加上-XX:+UseConcMarkSweepGC,这个参数表示对于老年代的回收采用CMS。CMS采用的基础算... 查看详情

类的加载机制(代码片段)

...们主要是对双亲委派机制进行详细讲解:前面我们知道类加载有系统自带的3种加载器,也有自定义的加载器,那么这些加载器之间的关系是什么,已经在加载类的时候,谁去加载呢?这节,我们将进行讲解。一、双亲委派机制JV... 查看详情

类的加载机制(代码片段)

首先,我们以一个最经典的例子来让大家知道什么是类的加载机制,上代码:classSingletonprivatestaticSingletonsingleton=newSingleton();privatestaticintcounter1;privatestaticintcounter2=0;publicSingleton()counter1++;counter2++;publicstati 查看详情

双亲委派机制(代码片段)

双亲委派机制双亲委派的原理:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶... 查看详情

帝国cms定时自动更新带加密插件(代码片段)

...要后台一直开启才能生效;现在需要做一个带密码的刷新插件,自动定时更新首页。1<?php2/********3*1.本插件目录位置在/e/admin/之下,4*2.新建日期202011305*3.chig@foxmail.com67***********/8$password=‘123456‘;//建议修改9if($password!=$_ 查看详情

应用程序到插件 django cms 错误

】应用程序到插件djangocms错误【英文标题】:apptoplugindjangocmserror【发布时间】:2018-09-1913:10:02【问题描述】:我想通过一个应用程序在网页上创建一个插件,但是,当将插件加载到页面时不会加载它的内容,而是在应用程序的... 查看详情

Django CMS 自定义插件从 cms_title 加载数据

】DjangoCMS自定义插件从cms_title加载数据【英文标题】:DjangoCMScustompluginloaddatafromcms_title【发布时间】:2016-11-1704:37:06【问题描述】:我想为DjangoCMS创建一个自定义插件。正如guide所示,我创建了一些示例。但现在的目标是创建一... 查看详情

类的加载机制(代码片段)

...i。由于自己也才入门Java不久,只是大概记得是因为Java类加载机制的问题,所以在认真查找了一下资料,并转载一下别人的博客,来 查看详情

类的加载机制(代码片段)

一.目标:1.什么是类的加载?2.类的生命周期?3.类加载器是什么?4.双亲委派机制是什么?二.原理(类的加载过程及其最终产品):JVM将class文件字节码文件加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,... 查看详情

jvm类加载机制一(代码片段)

类加载的过程什么是类加载?Java编译器会将我们编写好的代码编译成class字节码文件,JVM会把这些class字节码文件加载到内存中,并对加载的数据进行校验、准备、解析并初始化,这个过程就是类加载机制。类加载分为三个阶段... 查看详情

类加载机制(代码片段)

概述与很多服务器应用一样,Tomcat也安装了各种类加载器(那就是实现了 java.lang.ClassLoader 的类)。借助类加载器,容器的不同部分以及运行在容器上的Web应用就可以访问不同的仓库(保存着可使用的类和资源)。这个机... 查看详情

类加载机制(代码片段)

类是在运行期间动态加载的。类的生命周期 包括以下7个阶段:加载(Loading)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)使用(Using)卸载(Unloading)其中解析过程在某些情况下可以在初始化阶... 查看详情

jq插件机制(代码片段)

JQ插件概述所谓的jquery插件,其实就是利用jquery语法,完成的一些工具或者模块.jquery插件是jQuery功能的扩展.可以让开发人员能更好更快速的完成某些特定的需求,只需要用很少的代码就能实现很好的效果。自定义插件JQ允许自定义插... 查看详情

插件化开发详解(代码片段)

1:替换DexElements流程:插件化原理:https://www.cnblogs.com/wnpp/p/16053088.html插件生成apk,宿主通过反射机制和类加载器(传入插件apk),获取到插件的dexElements,并将dexElements合并到宿主的类加载器的dexElements,这样插件所有的class都位... 查看详情

jvm之类加载机制(代码片段)

JVM之类加载机制JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。 类加载五部分加载加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang... 查看详情