zoukankan      html  css  js  c++  java
  • 纸壳CMS的插件加载机制

    纸壳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);                
    });
  • 相关阅读:
    iOS应用程序间共享数据(转)
    解决右滑返回手势和UIScrollView中的手势冲突(转)
    (转)iOS被开发者遗忘在角落的NSException-其实它很强大
    iOS 身份证最后一位是X,输入17位后自动补全X(转)
    springboot单机秒杀之queue队列
    springboot单机秒杀-aop+锁
    springbot单机秒杀,锁与事务之间的大坑
    spring-cloud学习之4.微服务请求打通
    spring-cloud学习之3.使用feign实现负载均衡
    spring-cloud学习之2.搭建请求网关spring-cloud-getway
  • 原文地址:https://www.cnblogs.com/seriawei/p/9537867.html
Copyright © 2011-2022 走看看