ZKEACMS for .Net Core深度解析
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",最后按照页面的布局来显示。
zkeacms 请求流程图
驱动页面组件:
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深度解析
-
.NET Core源码解析配置文件及依赖注入
-
ZKEACMS for .Net Core深度解析
-
.Net Core Cors中间件解析
-
解析如何利用一个ASP.NET Core应用来发布静态文件
-
Asp.Net Core2.2 源码阅读系列——控制台日志源码解析
-
.NET Core实战项目之CMS 第三章 入门篇-源码解析配置文件及依赖注入
-
使用dotnet-dump 查找 .net core 3.0 占用CPU 100%的原因解析
-
《ASP.NET Core In Action》读书笔记系列五 ASP.NET Core 解决方案结构解析1
-
.Net Core中ObjectPool的使用与源码解析