ZKEACMS 简介

ZKEACMS.Core 是基于 .Net Core MVC 开发的开源CMS。ZKEACMS可以让用户自由规划页面布局,使用可视化编辑设计“所见即所得”,直接在页面上进行拖放添加内容。

ZKEACMS使用插件式设计,模块分离,通过横向扩展来丰富CMS的功能。

响应式设计

ZKEACMS使用Bootstrap3的栅格系统来实现响应式设计,从而实现在不同的设备上都可以正常访问。同时站在Bootstrap巨人的肩膀上,有丰富的主题资源可以使用。

简单演示


接下来看看程序设计及原理

项目结构

  • EasyFrameWork  底层框架
  • ZKEACMS   CMS核心
  • ZKEACMS.Article   文章插件
  • ZKEACMS.Product  产品插件
  • ZKEACMS.SectionWidget  模板组件插件
  • ZKEACMS.WebHost

原理 - 访问请求流程

路由在ZKEACMS里面起到了关键性的作用,通过路由的优先级来决定访问的流程走向,如果找到匹配的路由,则优先走该路由对应的 Controller -> Action -> View,如果没有匹配的路由,则走路由优先权最低的“全捕捉”路由来处理用户的请求,最后返回响应。

优先级最低的“全捕捉”路由是用来处理用户自行创建的页面的。当请求进来时,先去数据库中查找是否存在该页面,不存在则返回404。找到页面之后,再找出这个页面所有的组件、内容,然后统一调用各个组件的“Display"方法来来得到对应的“ViewModel"和视图"View",最后按照页面的布局来显示。

驱动页面组件:

  1. widgetService.GetAllByPage(filterContext.HttpContext.RequestServices, page).Each(widget =>
  2. {
  3. if (widget != null)
  4. {
  5. IWidgetPartDriver partDriver = widget.CreateServiceInstance(filterContext.HttpContext.RequestServices);
  6. WidgetViewModelPart part = partDriver.Display(widget, filterContext);
  7. lock (layout.ZoneWidgets)
  8. {
  9. if (layout.ZoneWidgets.ContainsKey(part.Widget.ZoneID))
  10. {
  11. layout.ZoneWidgets[part.Widget.ZoneID].TryAdd(part);
  12. }
  13. else
  14. {
  15. layout.ZoneWidgets.Add(part.Widget.ZoneID, new WidgetCollection { part });
  16. }
  17. }
  18. partDriver.Dispose();
  19. }
  20. });

页面呈现:

  1. foreach (var widgetPart in Model.ZoneWidgets[zoneId].OrderBy(m => m.Widget.Position).ThenBy(m => m.Widget.WidgetName))
  2. {
  3. <div style="@widgetPart.Widget.CustomStyle">
  4. <div class="widget @widgetPart.Widget.CustomClass">
  5. @if (widgetPart.Widget.Title.IsNotNullAndWhiteSpace())
  6. {
  7. <div class="panel panel-default">
  8. <div class="panel-heading">
  9. @widgetPart.Widget.Title
  10. </div>
  11. <div class="panel-body">
  12. @Html.DisPlayWidget(widgetPart)
  13. </div>
  14. </div>
  15. }
  16. else
  17. {
  18. @Html.DisPlayWidget(widgetPart)
  19. }
  20. </div>
  21. </div>
  22. }

插件“最关键”的类 PluginBase

每一个插件/模块都必需要一个类继承PluginBase,作为插件初始化的入口,程序在启动的时候,会加载这些类并作一些关键的初始化工作。

  1. public abstract class PluginBase : ResourceManager, IRouteRegister, IPluginStartup
  2. {
  3. public abstract IEnumerable<RouteDescriptor> RegistRoute(); //注册该插件所需要的路由 可返回空
  4. public abstract IEnumerable<AdminMenu> AdminMenu(); //插件在后端提供的菜单 可返回空
  5. public abstract IEnumerable<PermissionDescriptor> RegistPermission(); //注册插件的权限
  6. public abstract IEnumerable<Type> WidgetServiceTypes(); //返回该插件中提供的所有组件的类型
  7. public abstract void ConfigureServices(IServiceCollection serviceCollection); //IOC 注册对应的接口与实现
  8. public virtual void InitPlug(); //初始化插件,在程序启动时调用该方法
  9. }

具体实现可以参考“文章”插件 ArticlePlug.cs 或者“产品”插件 ProductPlug.cs

加载插件 Startup.cs

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.UseEasyFrameWork(Configuration).LoadEnablePlugins(plugin =>
  4. {
  5. var cmsPlugin = plugin as PluginBase;
  6. if (cmsPlugin != null)
  7. {
  8. cmsPlugin.InitPlug();
  9. }
  10. }, null);
  11. }

组件构成

一个页面,由许多的组件构成,每个组件都可以包含不同的内容(Content),像文字,图片,视频等,内容由组件决定,呈现方式由组件的模板(View)决定。

关系与呈现方式大致如下图所示:

实体 Enity

每个组件都会对应一个实体,用于存储与该组件相关的一些信息。实体必需继承于 BasicWidget 类。

例如HTML组件的实体类:

  1. [ViewConfigure(typeof(HtmlWidgetMetaData)), Table("HtmlWidget")]
  2. public class HtmlWidget : BasicWidget
  3. {
  4. public string HTML { get; set; }
  5. }
  6. class HtmlWidgetMetaData : WidgetMetaData<HtmlWidget>
  7. {
  8. protected override void ViewConfigure()
  9. {
  10. base.ViewConfigure();
  11. ViewConfig(m => m.HTML).AsTextArea().AddClass("html").Order(NextOrder());
  12. }
  13. }

实体类里面使用到了元数据配置[ViewConfigure(typeof(HtmlWidgetMetaData))],通过简单的设置来控制表单页面、列表页面的显示。假如设置为文本或下拉框;必填,长度等的验证。

这里实现方式是向MVC里面添加一个新的ModelMetadataDetailsProviderProvider,这个Provider的作用就是抓取这些元数据的配置信息并提交给MVC。

  1. services.AddMvc(option =>
  2. {
  3. option.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider());
  4. })

服务 Service

WidgetService 是数据与模板的桥梁,通过Service抓取数据并送给页面模板。 Service 必需继承自 WidgetService<WidgetBase, CMSDbContext>。如果业务复杂,则重写(override)基类的对应方法来实现。

例如HTML组件的Service:

  1. public class HtmlWidgetService : WidgetService<HtmlWidget, CMSDbContext>
  2. {
  3. public HtmlWidgetService(IWidgetBasePartService widgetService, IApplicationContext applicationContext)
  4. : base(widgetService, applicationContext)
  5. {
  6. }
  7.  
  8. public override DbSet<HtmlWidget> CurrentDbSet
  9. {
  10. get
  11. {
  12. return DbContext.HtmlWidget;
  13. }
  14. }
  15. }

视图实体 ViewModel

ViewModel 不是必需的,当实体(Entity)作为ViewModel传到视图不足以满足要求时,可以新建一个ViewModel,并将这个ViewModel传过去,这将要求重写 Display 方法

  1. public override WidgetViewModelPart Display(WidgetBase widget, ActionContext actionContext)
  2. {
  3. //do some thing
  4. return widget.ToWidgetViewModelPart(new ViewModel());
  5. }

视图 / 模板 Widget.cshtml

模板 (Template) 用于显示内容。通过了Service收集到了模板所要的“Model”,最后模板把它们显示出来。

动态编译分散的模板

插件的资源都在各自的文件夹下面,默认的视图引擎(ViewEngine)并不能找到这些视图并进行编译。MVC4版本的ZKEACMS是通过重写了ViewEngine来得以实现。.net core mvc 可以更方便实现了,实现自己的 ConfigureOptions<RazorViewEngineOptions> ,然后通过依赖注入就行。

  1. public class PluginRazorViewEngineOptionsSetup : ConfigureOptions<RazorViewEngineOptions>
  2. {
  3. public PluginRazorViewEngineOptionsSetup(IHostingEnvironment hostingEnvironment, IPluginLoader loader) :
  4. base(options => ConfigureRazor(options, hostingEnvironment, loader))
  5. {
  6.  
  7. }
  8. private static void ConfigureRazor(RazorViewEngineOptions options, IHostingEnvironment hostingEnvironment, IPluginLoader loader)
  9. {
  10. if (hostingEnvironment.IsDevelopment())
  11. {
  12. options.FileProviders.Add(new DeveloperViewFileProvider());
  13. }
  14. loader.GetPluginAssemblies().Each(assembly =>
  15. {
  16. var reference = MetadataReference.CreateFromFile(assembly.Location);
  17. options.AdditionalCompilationReferences.Add(reference);
  18. });
  19. loader.GetPlugins().Where(m => m.Enable && m.ID.IsNotNullAndWhiteSpace()).Each(m =>
  20. {
  21. var directory = new DirectoryInfo(m.RelativePath);
  22. if (hostingEnvironment.IsDevelopment())
  23. {
  24. options.ViewLocationFormats.Add($"/Porject.RootPath/{directory.Name}" + "/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
  25. options.ViewLocationFormats.Add($"/Porject.RootPath/{directory.Name}" + "/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
  26. options.ViewLocationFormats.Add($"/Porject.RootPath/{directory.Name}" + "/Views/{0}" + RazorViewEngine.ViewExtension);
  27. }
  28. else
  29. {
  30. options.ViewLocationFormats.Add($"/{Loader.PluginFolder}/{directory.Name}" + "/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
  31. options.ViewLocationFormats.Add($"/{Loader.PluginFolder}/{directory.Name}" + "/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
  32. options.ViewLocationFormats.Add($"/{Loader.PluginFolder}/{directory.Name}" + "/Views/{0}" + RazorViewEngine.ViewExtension);
  33. }
  34. });
  35. options.ViewLocationFormats.Add("/Views/{0}" + RazorViewEngine.ViewExtension);
  36. }
  37. }

看上面代码您可能会产生疑惑,为什么要分开发环境。这是因为ZKEACMS发布和开发的时候的文件夹目录结构不同造成的。为了方便开发,所以加入了开发环境的特别处理。接下来就是注入这个配置:

  1. services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, PluginRazorViewEngineOptionsSetup>());

  

EntityFrameWork

ZKEACMS for .net core 使用EntityFrameWork作为数据库访问。数据库相关配置 EntityFrameWorkConfigure

  1. public class EntityFrameWorkConfigure : IOnConfiguring
  2. {
  3. public void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  4. {
  5. optionsBuilder.UseSqlServer(Easy.Builder.Configuration.GetSection("ConnectionStrings")["DefaultConnection"]);
  6. }
  7. }

  

对Entity的配置依然可以直接写在对应的类或属性上。如果想使用 Entity Framework Fluent API,那么请创建一个类,并继承自 IOnModelCreating

  1. class EntityFrameWorkModelCreating : IOnModelCreating
  2. {
  3. public void OnModelCreating(ModelBuilder modelBuilder)
  4. {
  5. modelBuilder.Entity<LayoutHtml>().Ignore(m => m.Description).Ignore(m => m.Status).Ignore(m => m.Title);
  6. }
  7. }

  

主题

ZKEACMS 使用Bootstrap3作为基础,使用LESS,定议了许多的变量,像边距,颜色,背景等等,可以通过简单的修改变量就能“编译”出一个自己的主题。

或者也可以直接使用已经有的Bootstrap3的主题作为基础,然后快速创建主题。

最后

关于ZKEACMS还有很多,如果您也感兴趣,欢迎加入我们。

ZKEACMS for .net core 就是要让建网站变得更简单,快速。页面的修改与改版也变得更轻松,便捷。

ZKEACMS for .Net Core 深度解析的更多相关文章

  1. NVIDIA深度学习Tensor Core性能解析(下)

    NVIDIA深度学习Tensor Core性能解析(下) DeepBench推理测试之RNN和Sparse GEMM DeepBench的最后一项推理测试是RNN和Sparse GEMM,虽然测试中可 ...

  2. NVIDIA深度学习Tensor Core性能解析(上)

    NVIDIA深度学习Tensor Core性能解析(上) 本篇将通过多项测试来考验Volta架构,利用各种深度学习框架来了解Tensor Core的性能. 很多时候,深度学习这样的新领域会让人难以理解 ...

  3. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  4. 深度解析JQuery Dom元素操作技巧

    深度解析JQuery Dom元素操作技巧 DOM是一种与浏览器.平台.语言无关的接口,使用该接口可以轻松访问页面中所有的标准组件,这篇文章给大家介绍了JQuery dom元素操作方法,写的十分的全面细 ...

  5. spring源码深度解析— IOC 之 容器的基本实现

    概述 上一篇我们搭建完Spring源码阅读环境,spring源码深度解析—Spring的整体架构和环境搭建 这篇我们开始真正的阅读Spring的源码,分析spring的源码之前我们先来简单回顾下spr ...

  6. Spring源码深度解析之Spring MVC

    Spring源码深度解析之Spring MVC Spring框架提供了构建Web应用程序的全功能MVC模块.通过策略接口,Spring框架是高度可配置的,而且支持多种视图技术,例如JavaServer ...

  7. Tensor Core技术解析(下)

    Tensor Core技术解析(下) 让FP16适用于深度学习 Volta的深度学习能力是建立在利用半精度浮点(IEEE-754 FP16)而非单精度浮点(FP32)进行深度学习训练的基础之上. 该能 ...

  8. Tensor Core技术解析(上)

    Tensor Core技术解析(上) NVIDIA在SIGGRAPH 2018上正式发布了新一代GPU架构--Turing(图灵),黄仁勋称Turing架构是自2006年CUDA GPU发明以来最大的 ...

  9. [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析

    [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...

随机推荐

  1. 「小程序JAVA实战」小程序头像图片上传(中)(44)

    转自:https://idig8.com/2018/09/09/xiaochengxujavashizhanxiaochengxutouxiangtupianshangchuan43/ 用户可以上传了 ...

  2. **python实现的单例模式

    设计模式中,最简单的一个就是 “单例模式”. 所谓单例,是指一个类只有一个全局实例. 单例模式的使用场景: 1. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉 ...

  3. delphi IOS 后台状态保存

    FormSaveState procedure TFrm.FormSaveState(Sender: TObject);begin end; http://stackoverflow.com/ques ...

  4. dart 公共变量

    dart中可以直接在一个文件里声明一个变量,这在其他语言中并不常见,比如c#语言只有类型才可以在命名空间下定义,变量必须放在类里声明 所以dart这点特性类似于js 今天就来讨论这个公共变量的作用范围 ...

  5. Thinking in 查询设计

    近日,互联网动物园的各位小伙伴们召开了一次会议,考虑到大火的电子商务,准备在动物园里开发一个电商系统.首先上台的是销售山鸡,清了清嗓子,说道,人类正在进行电商革命,动物园也需要上一个电商系统,必要性有 ...

  6. 226. Invert Binary Tree 翻转二叉树

    [抄题]: Invert a binary tree. 4 / \ 2 7 / \ / \ 1 3 6 9 to 4 / \ 7 2 / \ / \ 9 6 3 1 [暴力解法]: 时间分析: 空间分 ...

  7. Linux的kickstart安装详解

    Linux的kickstart安装详解 一.什么是kickstart? kickstart安装是redhat开创的按照你设计好的方式全自动安装系统的方式.安装方式可以分为光盘.硬盘.和网络.此文将以网 ...

  8. ConcurrentHashMap的实现原理与使用

    一.适应ConcurrentHashMap的原因 HashMap存在线程不安全的问题,HashTable效率十分低下,因此,ConcurrentHashMap有了合适的登场机会. (1)HashTab ...

  9. Linux安装设置VNC远程桌面

    1,先检查一下服务器是否已经安装了VNC服务,没有安装,检查服务器的是否安装VNC的命令如下[root@linuxidc rpms]# ps -eaf|grep vncroot      1789  ...

  10. C程序之包含头文件

    在C程序中包含文件有以下两种方法: 方法一:#include<XXX.h> 这里的XXX一般是改动较小的标准库,用符号"<"和">"将要 ...