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",最后按照页面的布局来显示。

驱动页面组件:

widgetService.GetAllByPage(filterContext.HttpContext.RequestServices, page).Each(widget =>
{
     if (widget != null )
     {
         IWidgetPartDriver partDriver = widget.CreateServiceInstance(filterContext.HttpContext.RequestServices);
         WidgetViewModelPart part = partDriver.Display(widget, filterContext);
         lock (layout.ZoneWidgets)
         {
             if (layout.ZoneWidgets.ContainsKey(part.Widget.ZoneID))
             {
                 layout.ZoneWidgets[part.Widget.ZoneID].TryAdd(part);
             }
             else
             {
                 layout.ZoneWidgets.Add(part.Widget.ZoneID, new WidgetCollection { part });
             }
         }
         partDriver.Dispose();
     }
});

页面呈现:

foreach ( var widgetPart in Model.ZoneWidgets[zoneId].OrderBy(m => m.Widget.Position).ThenBy(m => m.Widget.WidgetName))
{
     <div style= "@widgetPart.Widget.CustomStyle" >
         <div class = "widget @widgetPart.Widget.CustomClass" >
             @ if (widgetPart.Widget.Title.IsNotNullAndWhiteSpace())
             {
                 <div class = "panel panel-default" >
                     <div class = "panel-heading" >
                         @widgetPart.Widget.Title
                     </div>
                     <div class = "panel-body" >
                         @Html.DisPlayWidget(widgetPart)
                     </div>
                 </div>
             }
             else
             {
                 @Html.DisPlayWidget(widgetPart)
             }
         </div>
     </div>
}

插件“最关键”的类 PluginBase

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

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

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

加载插件 Startup.cs

public void ConfigureServices(IServiceCollection services)
{
     services.UseEasyFrameWork(Configuration).LoadEnablePlugins(plugin =>
     {
         var cmsPlugin = plugin as PluginBase;
         if (cmsPlugin != null )
         {
             cmsPlugin.InitPlug();
         }
     }, null );           
}

组件构成

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

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

实体 Enity

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

例如HTML组件的实体类:

[ViewConfigure( typeof (HtmlWidgetMetaData)), Table( "HtmlWidget" )]
public class HtmlWidget : BasicWidget
{
     public string HTML { get ; set ; }
}
class HtmlWidgetMetaData : WidgetMetaData<HtmlWidget>
{
     protected override void ViewConfigure()
     {
         base .ViewConfigure();
         ViewConfig(m => m.HTML).AsTextArea().AddClass( "html" ).Order(NextOrder());
     }
}

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

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

services.AddMvc(option =>
     {
         option.ModelMetadataDetailsProviders.Add( new DataAnnotationsMetadataProvider());
     })

服务 Service

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

例如HTML组件的Service:

public class HtmlWidgetService : WidgetService<HtmlWidget, CMSDbContext>
{
     public HtmlWidgetService(IWidgetBasePartService widgetService, IApplicationContext applicationContext)
         : base (widgetService, applicationContext)
     {
     }
     public override DbSet<HtmlWidget> CurrentDbSet
     {
         get
         {
             return DbContext.HtmlWidget;
         }
     }
}

视图实体 ViewModel

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

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

视图 / 模板 Widget.cshtml

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

动态编译分散的模板

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

public class PluginRazorViewEngineOptionsSetup : ConfigureOptions<RazorViewEngineOptions>
{
     public PluginRazorViewEngineOptionsSetup(IHostingEnvironment hostingEnvironment, IPluginLoader loader) :
         base (options => ConfigureRazor(options, hostingEnvironment, loader))
     {
     }
     private static void ConfigureRazor(RazorViewEngineOptions options, IHostingEnvironment hostingEnvironment, IPluginLoader loader)
     {
         if (hostingEnvironment.IsDevelopment())
         {
             options.FileProviders.Add( new DeveloperViewFileProvider());
         }
         loader.GetPluginAssemblies().Each(assembly =>
         {
             var reference = MetadataReference.CreateFromFile(assembly.Location);
             options.AdditionalCompilationReferences.Add(reference);               
         });
         loader.GetPlugins().Where(m => m.Enable && m.ID.IsNotNullAndWhiteSpace()).Each(m =>
         {
             var directory = new DirectoryInfo(m.RelativePath);
             if (hostingEnvironment.IsDevelopment())
             {
                 options.ViewLocationFormats.Add($ "/Porject.RootPath/{directory.Name}" + "/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
                 options.ViewLocationFormats.Add($ "/Porject.RootPath/{directory.Name}" + "/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
                 options.ViewLocationFormats.Add($ "/Porject.RootPath/{directory.Name}" + "/Views/{0}" + RazorViewEngine.ViewExtension);
             }
             else
             {
                 options.ViewLocationFormats.Add($ "/{Loader.PluginFolder}/{directory.Name}" + "/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
                 options.ViewLocationFormats.Add($ "/{Loader.PluginFolder}/{directory.Name}" + "/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
                 options.ViewLocationFormats.Add($ "/{Loader.PluginFolder}/{directory.Name}" + "/Views/{0}" + RazorViewEngine.ViewExtension);
             }
         });
         options.ViewLocationFormats.Add( "/Views/{0}" + RazorViewEngine.ViewExtension);
     }
}

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

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

  

EntityFrameWork

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

public class EntityFrameWorkConfigure : IOnConfiguring
{
     public void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
     {
         optionsBuilder.UseSqlServer(Easy.Builder.Configuration.GetSection( "ConnectionStrings" )[ "DefaultConnection" ]);
     }
}

  

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

class EntityFrameWorkModelCreating : IOnModelCreating
{
     public void OnModelCreating(ModelBuilder modelBuilder)
     {
         modelBuilder.Entity<LayoutHtml>().Ignore(m => m.Description).Ignore(m => m.Status).Ignore(m => m.Title);
     }
}

  

主题

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

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

最后

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

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

原文地址:http://www.cnblogs.com/seriawei/p/6540235.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

赞赏

人赞赏

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. Tensor Core技术解析(上)

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

  4. 限时团购,6.9折:《微信开发深度解析:公众号、小程序高效开发秘籍》推荐序

    全书由目 Senparc.Weixin SDK 作者苏震巍历时 2 年完成,涵盖了开发微信公众号及小程序需要用的的各项后端开发技能.技巧.避坑提示,以及 Senparc.Weixin SDK 微信公众 ...

  5. 算力寻租或将终结中本聪的POW机制?深度解析BCH“司机补贴战”

    文章转自碳链价值,作者唐晗,经作者授权转发 作者 | 唐晗 编辑 | 秦晋 编注:上周,为掌控 BCH 而开展的"哈希战争(HashWar)",让"澳本聪"和比 ...

  6. 《能屈能伸英特尔睿频加速技术深度解析》

    阅读<能屈能伸英特尔睿频加速技术深度解析>的小笔记 睿频加速:根据需要,自动调节多个CPU内核的负载以达到最佳运算的效果.支持每个处理器内的特定内核在设定的范围内以超出额定频率的频率运行, ...

  7. Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(三)-Controller 解析

    在之前的博客中Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一),己经对 Spring MVC 的框架做了详细的分析,但是有一个问题,发现举的例子不常用,因为我们在实际开发项 ...

  8. Excel文件的深度解析

    文章目录 Excel深度解析历程 故事背景 技术选型方案 一. 直接采用现成的框架 二. 自己处理解析 遇到的困难 升级的思路 一.大胆的创想 二.深度解析实践 第一步 第二步 第三步 性能测试 性能 ...

  9. NVIDIA Grace Hopper架构深度解析

    NVIDIA Grace Hopper架构深度解析 NVIDIA Grace Hopper Superchip 架构是第一个真正的异构加速平台,适用于高性能计算 (HPC) 和 AI 工作负载. 它利 ...

最新文章

  1. 华为秘密作战计划曝光,重注研发AI芯片挑战英伟达,轮值董事长挂帅
  2. 【android】【转】class android.media.MediaPlayer
  3. 洛谷 - P6292 区间本质不同子串个数(SAM+LCT+线段树)
  4. [机器学习] XGBoost参数调优完全指南(附Python代码)
  5. 一般纳米材料是指尺度为_纳米是什么米?什么是纳米材料?(1)
  6. 想旷工被单位开除领取失业金,可是单位不但不开除还给交社保,该怎么办?
  7. 你们身边成功的生意人有哪些特质
  8. LSTM模型实战案例:TensorFlow实现预测3位彩票号码
  9. 计算机启动需输入两次密码,我的电脑开机的时候要输入2次密码,我应当怎么去除?...
  10. Django结合Bootstrap分页显示mysql中的值
  11. esp32摄像显示时间_ESP32彩屏显示入门:我要五彩斑斓的黑,还有五光十色的白
  12. 串的定义及其基本操作的实现
  13. tensorflow正则化添加方法整理
  14. elasticsearch 生产级别深度优化
  15. 什么是 Android Jetpack?
  16. 前端如何保存图片?并在相册中查看。
  17. KONG和KONGA部署及配置
  18. 飞鱼48小时游戏创作嘉年华_厦门Pitch Time总结与收获
  19. python股票基本面分析_股票基本面分析
  20. css布局 --- 流体布局 冻结布局 凝胶布局 绝对定位

热门文章

  1. AM335x kernel4.4.12 LCD 时钟翻转设置记录
  2. Linux入门之进程管理(4)之进程与文件
  3. linux下简单的备份的脚本 2 【转】
  4. Sublime Text 2 中运行 PHP
  5. django与easyui使用过程中遇到的问题
  6. cisco路由器NAT配置
  7. EasyNetQ操作RabbitMQ
  8. Blazor 数据绑定开发指南
  9. C# 调用动态链接库读取二代身份证信息
  10. 如何使用 C# 中的 ValueTuple