当前位置: 移动技术网 > IT编程>开发语言>.net > ZKEACMS for .Net Core深度解析

ZKEACMS for .Net Core深度解析

2017年12月08日  | 移动技术网IT编程  | 我要评论

luanluntuqu,金奇仕最新事件,崔克敏

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

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网