关键词:
纸壳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... 查看详情