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 就是要让建网站变得更简单,快速。页面的修改与改版也变得更轻松,便捷。

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. leetcode503

    public class Solution { public int[] NextGreaterElements(int[] nums) { int n = nums.Length; int[] ne ...

  2. delphi IOS 通知 TNotification

    delphi  IOS 通知 TNotification http://blogs.embarcadero.com/ao/2013/05/01/39450 TNotification http://d ...

  3. Shiro异常处理总结

    出自:https://blog.csdn.net/goodyuedandan/article/details/62420120 一.Spring MVC处理异常有3种方式: (1)使用Spring-M ...

  4. iOS开发基础控件--UILabel

    UILabel 的常见属性和方法: //创建UIlabel对象 UILabel* label = [[UILabel alloc] initWithFrame:self.view.bounds]; / ...

  5. 学习ios一段过程后的思考

    现在回想起来,学习ios也有一段时间了,大概三个月不到吧,本来是搞linux驱动,刚开始来公司就我一个人负责驱动的东西,主要就是一些bug的解决,后来系统基本上稳定了,我就闲下来了,公司又有些移动医疗 ...

  6. C#给图片加文字水印

    public class TxtWaterMark { public enum WaterPositionMode { LeftTop,//左上 LeftBottom,//左下 RightTop,// ...

  7. 02.全文检索和数据库like的区别

    全文检索主要应用领域:搜索引擎(百度,搜狗).站内搜索(微博搜索).电商网站(京东,淘宝) 现在不缺乏做java的人,但是缺乏有互联网背景的做Java的人.具有互联网技术的Java人才.比如说大数据, ...

  8. cannot launch node of type [arbotix_python/arbotix_driver]: arbotix_python

    这个时候提示错误: ERROR: cannot launch node of type [arbotix_python/arbotix_driver]: arbotix_python ROS path ...

  9. python用sqlite3模块操作sqlite数据库-乾颐堂

    SQLite是一个包含在C库中的轻量级数据库.它并不需要独立的维护进程,并且允许使用非标准变体(nonstandard variant)的SQL查询语句来访问数据库. 一些应用可是使用SQLite保存 ...

  10. MemoryUsage:监测java虚拟机内存使用

    通过MemoryUsage可以查看Java 虚拟机的内存池的内存使用情况.MemoryUsage类有四个值(均以字节为单位): ===Init=== java虚拟机在启动的时候向操作系统请求的初始内存 ...