纸壳CMS的插件加载机制
纸壳cms是一个开源的可视化设计cms,通过拖拽,在线编辑的方式来创建网站。
github
https://github.com/seriawei/zkeacms.core
欢迎star,fork,发pr。:)
插件化设计
纸壳cms是基于插件化设计的,可以通过扩展插件来实现不同的功能。如何通过插件来扩展,可以参考这篇文章:
纸壳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); });
上一篇: 聊聊IOCP,聊聊异步编程
下一篇: C#-命名空间(十五)