欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

纸壳CMS的插件加载机制

程序员文章站 2022-12-25 17:51:20
纸壳CMS是一个开源的可视化设计CMS,通过拖拽,在线编辑的方式来创建网站。纸壳CMS是基于插件化设计的,可以通过扩展插件来实现不同的功能。并且纸壳CMS的插件是相互独立的,各插件的引用也相互独立,即各插件都可引用各自需要的nuget包来达到目的。而不用把引用加到底层。 ......

纸壳cms是一个开源的可视化设计cms,通过拖拽,在线编辑的方式来创建网站。

github

https://github.com/seriawei/zkeacms.core

欢迎star,fork,发pr。:)

插件化设计

纸壳cms是基于插件化设计的,可以通过扩展插件来实现不同的功能。如何通过插件来扩展,可以参考这篇文章:

纸壳cms的插件是相互独立的,各插件的引用也相互独立,即各插件都可引用各自需要的nuget包来达到目的。而不用把引用加到底层。

插件存放目录

纸壳cms的插件的存放目录在开发环境和已发布的程序中是不一样的。在开发环境,插件和其它的项目统一放在src目录下:

纸壳CMS的插件加载机制

而发布程序以后,插件会在wwwroot/plugins目录下:

所以,如果在开发过程中要使用插件目录时,需要使用特定的方法来获取真实的目录,如:

pluginbase.getpath<sectionplug>()

 

相关代码

有关插件用到的所有相关代码,都在 easyframework/mvc/plugin 目录下:

纸壳CMS的插件加载机制

插件加载

纸壳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);                
});