zkeacms.core 是基于 .net core mvc 开发的开源cms,这篇文章主要为大家深度解析了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))
{
<p style="@widgetpart.widget.customstyle">
<p class="widget @widgetpart.widget.customclass">
@if (widgetpart.widget.title.isnotnullandwhitespace())
{
<p class="panel panel-default">
<p class="panel-heading">
@widgetpart.widget.title
</p>
<p class="panel-body">
@html.displaywidget(widgetpart)
</p>
</p>
}
else
{
@html.displaywidget(widgetpart)
}
</p>
</p>
}
插件“最关键”的类 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 就是要让建网站变得更简单,快速。页面的修改与改版也变得更轻松,便捷。
【相关推荐】
1. .net core 之 图形验证码
2. .net core配置文件加载与di注入配置数据
3. .net core cli工具文档dotnet-publish
4. 分享.net mvc中使用forms验证实例代码
5. 在.net core 下如何进行http请求?
6. centos上运行zkeacms的实例教程
以上就是详细介绍zkeacms for .net core的详细内容。