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

纸壳CMS的插件加载机制的更多相关文章

  1. 一种优雅的Golang的库插件注册加载机制

    一种优雅的Golang的库插件注册加载机制 你好,我是轩脉刃. 最近看到一个内部项目的插件加载机制,非常赞.当然这里说的插件并不是指的golang原生的可以在buildmode中加载指定so文件的那种 ...

  2. 插件化框架解读之so 文件加载机制(四)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 提问 本文的结论是跟着 System.loadlibrary() ...

  3. 使用vs code开发纸壳CMS并启用Razor智能提示

    关于纸壳CMS 纸壳CMS是一个开源免费的,可视化设计,在线编辑的内容管理系统.基于ASP .Net Core开发,插件式设计: 下载代码 GitHub:https://github.com/Seri ...

  4. ASP .Net Core路由(Route) - 纸壳CMS的关键

    关于纸壳CMS 纸壳CMS是一个开源免费的,可视化设计,在线编辑的内容管理系统.基于ASP .Net Core开发,插件式设计: GitHub:https://github.com/SeriaWei/ ...

  5. 纸壳CMS列表Grid的配置

    纸壳CMS(ZKEACMS)里的Grid是一个TagHelper,是对jQuery插件datatables的一个配置封装. Easy.Mvc.TagHelpers.GridTagHelper grid ...

  6. Java高级之虚拟机加载机制

    本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 1.0版本:2016-05-21 SubClass!! 执行结果说明一个问题:子类调用父类变量的时候 ...

  7. 纸壳CMS主题增强,支持主题中加入模板

    背景 在之前,纸壳CMS的主题仅仅只是CSS样式,并不支持在主题下使用模板来构建不同的HTML结构.现在我们对主题功能做了增强,可以在主题下添加各自的模板,这样在制作主题时,就会更加自由.不仅如此,新 ...

  8. Spring Boot 扩展点应用之工厂加载机制

    Spring 工厂加载机制,即 Spring Factories Loader,核心逻辑是使用 SpringFactoriesLoader 加载由用户实现的类,并配置在约定好的META-INF/spr ...

  9. Android之Android apk动态加载机制的研究(二):资源加载和activity生命周期管理

    转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/23387079 (来自singwhatiwanna的csdn博客) 前言 为了 ...

随机推荐

  1. 利用U盘安装Redhat-server-Linux-7.1

    利用U盘安装Redhat-server-Linux-7.1 [原]红帽 Red Hat Linux相关产品iso镜像下载[百度云]

  2. 服务器选型:x86 vs 小型机谁更胜一筹?

    市场上关于X86 和小型机的争论从来就没有停止过,在以往的印象当中,x86服务器在中低端形成了统治之势,而小型机则在关键性应用领域(金融.证券.政府等)享有王者地位.但是随着X86服务器的不断发展,这 ...

  3. 让IE依据HTML头标签选择显示模式

    文件兼容性用于定义让IE如何编译你的网页.此文件解释文件兼容性,如何指定你网站的文件兼容性模式以及如何判断一个网页该使用的文件模式. 前言 为了帮助确保你的网页在所有未来的IE版本都有一致的外观,IE ...

  4. leetcode204

    public class Solution { public int CountPrimes(int n) { ) { ; } ]; ]; ; ; i < n; i++) { mark[i] = ...

  5. Delphi Firemonkey Button ImageList

    Delphi Firemonkey Button ImageList 按钮图标 在上面 界面上,选择Button,放个ImageList控件,添加图标到ImageList. 然后关联Button和Im ...

  6. php遍历所有文件

    <?php function getdir($dir){ static $str = ''; if(is_file($dir)){ $str.=$dir.'<br>'; }else{ ...

  7. PO BO VO DTO POJO DAO概念及其作用

    J2EE开发中大量的专业缩略语很是让人迷惑,尤其是跟一些高手讨论问题的时候,三分钟就被人家满口的专业术语喷晕了,PO VO BO DTO POJO DAO,一大堆的就来了(听过老罗对这种现象的批判的朋 ...

  8. 【322】python控制键盘鼠标:pynput

    参考:python实战===python控制键盘鼠标:pynput 参考:[Python Study Notes]pynput实现对鼠标控制 参考:pynput doc 参考:pynput Packa ...

  9. Linux学习---新建文件,查看文件,修改权限,删除

    过程:在一个文件夹下面新建一个文件,然后查看文件,再修改权限,运行,最后删除 1.新建文件: touch  Test.sh 补充:新建文件有好多种方式,一般用mkdir(创建目录,即文件夹).touc ...

  10. Source命令及脚本的执行方式

    [Source命令及脚本的执行方式] source filename 与 sh filename 及./filename执行脚本的区别在那里呢? 1.当shell脚本具有可执行权限时,用sh file ...