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

原文链接:https://www.cnblogs.com/seriawei/p/9537867.html

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏古时的风筝

ASP.NET-自定义HttpModule与HttpHandler

在之前的ASP.NET是如何在IIS下工作的这篇文章中介绍了ASP.NET与IIS配合工作的机制,在http请求经过一系列处理后,最后到达ASP.NET管道中...

31880
来自专栏跟着阿笨一起玩NET

C#.Net前台线程与后台线程的区别

 if (!this.IsDisposed ) 也是可以的。判断当前窗体是否已经释放。(备注:释放的窗体是不能操作界面UI元素的。)

34110
来自专栏跟着阿笨一起玩NET

DataTable的AcceptChange方法为什么不能在Update之前?

本人转载http://www.cnblogs.com/telnet_mike/archive/2011/08/29/2159013.html

16410
来自专栏菩提树下的杨过

.net中的认证(authentication)与授权(authorization)

注:这篇文章主要给新手看的,老手们可能会觉得没啥营养,就请绕过吧。 “认证”与“授权”是几乎所有系统中都会涉及的概念,通俗点讲: 认证(authenticat...

378100
来自专栏恰童鞋骚年

.NET Core微服务之基于MassTransit实现数据最终一致性(Part 1)

  关于数据一致性的文章,园子里已经有很多了,如果你还不了解,那么可以通过以下的几篇文章去快速地了解了解,有个感性认识即可。

12830
来自专栏飞扬的花生

ASP.MVC 基于AuthorizeAttribute权限设计案例

     ASP.MVC上实现权限控制的方法很多,比如使用AuthorizeAttribute这个特性 1.创建自定义特性用于权限验证 public clas...

24370
来自专栏.NET开发者社区

一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](四)

上一篇《一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](三)》,我们完成了:

22990
来自专栏菩提树下的杨过

img标签的src=""会引起的Page_Load多次执行

今天看见园子里有人因img的src为空导致session丢失,详情见http://www.cnblogs.com/kyneblog/archive/2009/0...

229100
来自专栏菩提树下的杨过

Sqlite快速上手使用指南

这是网上收集的几篇教程 1. Sqlite简明教程 http://www.sqlite.com.cn/MySqlite/4/32.Html 2. Sqlite入...

25590
来自专栏程序员的SOD蜜

常见.NET功能代码汇总

1,在Web上修改指定文件位置的Web.config 这里需要使用 WebConfigurationManager 类,但必须使用WebConfiguratio...

458100

扫码关注云+社区

领取腾讯云代金券