欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ZKEACMS for .Net Core深度解析

程序员文章站 2023-11-18 17:44:34
zkeacms 简介 zkeacms.core 是基于 .net core mvc 开发的开源cms。zkeacms可以让用户*规划页面布局,使用可视化编辑设计“所见即...

zkeacms 简介
zkeacms.core 是基于 .net core mvc 开发的开源cms。zkeacms可以让用户*规划页面布局,使用可视化编辑设计“所见即所得”,直接在页面上进行拖放添加内容。

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

响应式设计

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

简单演示

ZKEACMS for .Net Core深度解析

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

项目结构

  • easyframework  底层框架
  • zkeacms   cms核心
  • zkeacms.article   文章插件
  • zkeacms.product  产品插件
  • zkeacms.sectionwidget  模板组件插件
  • zkeacms.webhost 

原理 - 访问请求流程

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

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

zkeacms 请求流程图

ZKEACMS for .Net Core深度解析

驱动页面组件:

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)决定。

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

ZKEACMS for .Net Core深度解析

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。