ZKEACMS for .Net Core深度解析
ZKEACMS 簡(jiǎn)介
ZKEACMS.Core 是基于 .Net Core MVC 開(kāi)發(fā)的開(kāi)源CMS。ZKEACMS可以讓用戶自由規(guī)劃頁(yè)面布局,使用可視化編輯設(shè)計(jì)“所見(jiàn)即所得”,直接在頁(yè)面上進(jìn)行拖放添加內(nèi)容。
ZKEACMS使用插件式設(shè)計(jì),模塊分離,通過(guò)橫向擴(kuò)展來(lái)豐富CMS的功能。
響應(yīng)式設(shè)計(jì)
ZKEACMS使用Bootstrap3的柵格系統(tǒng)來(lái)實(shí)現(xiàn)響應(yīng)式設(shè)計(jì),從而實(shí)現(xiàn)在不同的設(shè)備上都可以正常訪問(wèn)。同時(shí)站在Bootstrap巨人的肩膀上,有豐富的主題資源可以使用。
簡(jiǎn)單演示

接下來(lái)看看程序設(shè)計(jì)及原理
項(xiàng)目結(jié)構(gòu)
- EasyFrameWork 底層框架
- ZKEACMS CMS核心
- ZKEACMS.Article 文章插件
- ZKEACMS.Product 產(chǎn)品插件
- ZKEACMS.SectionWidget 模板組件插件
- ZKEACMS.WebHost
原理 - 訪問(wèn)請(qǐng)求流程
路由在ZKEACMS里面起到了關(guān)鍵性的作用,通過(guò)路由的優(yōu)先級(jí)來(lái)決定訪問(wèn)的流程走向,如果找到匹配的路由,則優(yōu)先走該路由對(duì)應(yīng)的 Controller -> Action -> View,如果沒(méi)有匹配的路由,則走路由優(yōu)先權(quán)最低的“全捕捉”路由來(lái)處理用戶的請(qǐng)求,最后返回響應(yīng)。
優(yōu)先級(jí)最低的“全捕捉”路由是用來(lái)處理用戶自行創(chuàng)建的頁(yè)面的。當(dāng)請(qǐng)求進(jìn)來(lái)時(shí),先去數(shù)據(jù)庫(kù)中查找是否存在該頁(yè)面,不存在則返回404。找到頁(yè)面之后,再找出這個(gè)頁(yè)面所有的組件、內(nèi)容,然后統(tǒng)一調(diào)用各個(gè)組件的“Display"方法來(lái)來(lái)得到對(duì)應(yīng)的“ViewModel"和視圖"View",最后按照頁(yè)面的布局來(lái)顯示。
ZKEACMS 請(qǐng)求流程圖

驅(qū)動(dòng)頁(yè)面組件:
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();
}
});
頁(yè)面呈現(xiàn):
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>
}
插件“最關(guān)鍵”的類(lèi) PluginBase
每一個(gè)插件/模塊都必需要一個(gè)類(lèi)繼承PluginBase,作為插件初始化的入口,程序在啟動(dòng)的時(shí)候,會(huì)加載這些類(lèi)并作一些關(guān)鍵的初始化工作。
public abstract class PluginBase : ResourceManager, IRouteRegister, IPluginStartup
{
public abstract IEnumerable<RouteDescriptor> RegistRoute(); //注冊(cè)該插件所需要的路由 可返回空
public abstract IEnumerable<AdminMenu> AdminMenu(); //插件在后端提供的菜單 可返回空
public abstract IEnumerable<PermissionDescriptor> RegistPermission(); //注冊(cè)插件的權(quán)限
public abstract IEnumerable<Type> WidgetServiceTypes(); //返回該插件中提供的所有組件的類(lèi)型
public abstract void ConfigureServices(IServiceCollection serviceCollection); //IOC 注冊(cè)對(duì)應(yīng)的接口與實(shí)現(xiàn)
public virtual void InitPlug(); //初始化插件,在程序啟動(dòng)時(shí)調(diào)用該方法
}
具體實(shí)現(xiàn)可以參考“文章”插件 ArticlePlug.cs 或者“產(chǎn)品”插件 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);
}
組件構(gòu)成
一個(gè)頁(yè)面,由許多的組件構(gòu)成,每個(gè)組件都可以包含不同的內(nèi)容(Content),像文字,圖片,視頻等,內(nèi)容由組件決定,呈現(xiàn)方式由組件的模板(View)決定。
關(guān)系與呈現(xiàn)方式大致如下圖所示:

實(shí)體 Enity
每個(gè)組件都會(huì)對(duì)應(yīng)一個(gè)實(shí)體,用于存儲(chǔ)與該組件相關(guān)的一些信息。實(shí)體必需繼承于 BasicWidget 類(lèi)。
例如HTML組件的實(shí)體類(lèi):
[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());
}
}
實(shí)體類(lèi)里面使用到了元數(shù)據(jù)配置[ViewConfigure(typeof(HtmlWidgetMetaData))],通過(guò)簡(jiǎn)單的設(shè)置來(lái)控制表單頁(yè)面、列表頁(yè)面的顯示。假如設(shè)置為文本或下拉框;必填,長(zhǎng)度等的驗(yàn)證。
這里實(shí)現(xiàn)方式是向MVC里面添加一個(gè)新的ModelMetadataDetailsProviderProvider,這個(gè)Provider的作用就是抓取這些元數(shù)據(jù)的配置信息并提交給MVC。
services.AddMvc(option =>
{
option.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider());
})
服務(wù) Service
WidgetService 是數(shù)據(jù)與模板的橋梁,通過(guò)Service抓取數(shù)據(jù)并送給頁(yè)面模板。 Service 必需繼承自 WidgetService<WidgetBase, CMSDbContext>。如果業(yè)務(wù)復(fù)雜,則重寫(xiě)(override)基類(lèi)的對(duì)應(yīng)方法來(lái)實(shí)現(xiàn)。
例如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;
}
}
}
視圖實(shí)體 ViewModel
ViewModel 不是必需的,當(dāng)實(shí)體(Entity)作為ViewModel傳到視圖不足以滿足要求時(shí),可以新建一個(gè)ViewModel,并將這個(gè)ViewModel傳過(guò)去,這將要求重寫(xiě) Display 方法
public override WidgetViewModelPart Display(WidgetBase widget, ActionContext actionContext)
{
//do some thing
return widget.ToWidgetViewModelPart(new ViewModel());
}
視圖 / 模板 Widget.cshtml
模板 (Template) 用于顯示內(nèi)容。通過(guò)了Service收集到了模板所要的“Model”,最后模板把它們顯示出來(lái)。
動(dòng)態(tài)編譯分散的模板
插件的資源都在各自的文件夾下面,默認(rèn)的視圖引擎(ViewEngine)并不能找到這些視圖并進(jìn)行編譯。MVC4版本的ZKEACMS是通過(guò)重寫(xiě)了ViewEngine來(lái)得以實(shí)現(xiàn)。.net core mvc 可以更方便實(shí)現(xiàn)了,實(shí)現(xiàn)自己的 ConfigureOptions<RazorViewEngineOptions> ,然后通過(guò)依賴注入就行。
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);
}
}
看上面代碼您可能會(huì)產(chǎn)生疑惑,為什么要分開(kāi)發(fā)環(huán)境。這是因?yàn)閆KEACMS發(fā)布和開(kāi)發(fā)的時(shí)候的文件夾目錄結(jié)構(gòu)不同造成的。為了方便開(kāi)發(fā),所以加入了開(kāi)發(fā)環(huán)境的特別處理。接下來(lái)就是注入這個(gè)配置:
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, PluginRazorViewEngineOptionsSetup>());
EntityFrameWork
ZKEACMS for .net core 使用EntityFrameWork作為數(shù)據(jù)庫(kù)訪問(wèn)。數(shù)據(jù)庫(kù)相關(guān)配置 EntityFrameWorkConfigure
public class EntityFrameWorkConfigure : IOnConfiguring
{
public void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(Easy.Builder.Configuration.GetSection("ConnectionStrings")["DefaultConnection"]);
}
}
對(duì)Entity的配置依然可以直接寫(xiě)在對(duì)應(yīng)的類(lèi)或?qū)傩陨?。如果想使?Entity Framework Fluent API,那么請(qǐng)創(chuàng)建一個(gè)類(lèi),并繼承自 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作為基礎(chǔ),使用LESS,定議了許多的變量,像邊距,顏色,背景等等,可以通過(guò)簡(jiǎn)單的修改變量就能“編譯”出一個(gè)自己的主題。
或者也可以直接使用已經(jīng)有的Bootstrap3的主題作為基礎(chǔ),然后快速創(chuàng)建主題。
最后
關(guān)于ZKEACMS還有很多,如果您也感興趣,歡迎加入我們。
ZKEACMS for .net core 就是要讓建網(wǎng)站變得更簡(jiǎn)單,快速。頁(yè)面的修改與改版也變得更輕松,便捷。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
asp.net下用url重寫(xiě)URLReWriter實(shí)現(xiàn)任意二級(jí)域名的方法
asp.net下用url重寫(xiě)URLReWriter實(shí)現(xiàn)任意二級(jí)域名的方法...2007-03-03
asp.net網(wǎng)站實(shí)現(xiàn)接入QQ登錄示例代碼
相信大家在做開(kāi)發(fā)的時(shí)候,常會(huì)遇到集成QQ登錄的功能,本文主要說(shuō)的是利用asp.net代碼的實(shí)現(xiàn)方式,邏輯部分主要還是根據(jù)幫助文檔來(lái)的。不懂的同學(xué)可以先看看文檔。下面來(lái)一起學(xué)習(xí)學(xué)習(xí)。2016-08-08
asp.net core 騰訊驗(yàn)證碼的接入示例代碼
這篇文章主要介紹了asp.net core 騰訊驗(yàn)證碼的接入示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
ASP.NET?Core?實(shí)現(xiàn)自動(dòng)刷新JWT?Token
這篇文章主要介紹了ASP.NET?Core?實(shí)現(xiàn)自動(dòng)刷新JWT?Token,通過(guò)增加??refresh_token??,客戶端使用refresh_token去主動(dòng)刷新JWT?Token,下文具體操作過(guò)程需要的小伙伴可以參考一下2022-04-04
.NET使用.NET Core CLI開(kāi)發(fā)應(yīng)用程序
這篇文章主要為大家詳細(xì)介紹了.NET使用.NET Core CLI開(kāi)發(fā)應(yīng)用程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08
.NET使用YARP根據(jù)域名轉(zhuǎn)發(fā)實(shí)現(xiàn)反向代理
這篇文章介紹了.NET使用YARP根據(jù)域名轉(zhuǎn)發(fā)實(shí)現(xiàn)反向代理的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09
ASP.NET Core MVC 修改視圖的默認(rèn)路徑及其實(shí)現(xiàn)原理解析
本章將和大家分享如何在ASP.NET Core MVC中修改視圖的默認(rèn)路徑,以及它的實(shí)現(xiàn)原理,感興趣的朋友跟隨小編一起看看吧2021-09-09
IIS服務(wù)器發(fā)布ASP.NET項(xiàng)目
如何在云服務(wù)器上部署一個(gè)項(xiàng)目,需要做哪些配置準(zhǔn)備,本文就來(lái)介紹一下IIS服務(wù)器發(fā)布ASP.NET項(xiàng)目,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
解決VS2012 Express的There was a problem sending the command to
安裝Visual Studio 2012 Express之后,雙擊打開(kāi)web.config文件時(shí)經(jīng)常出現(xiàn)“There was a problem sending the command to the program”的錯(cuò)誤,然后VS2012 Express打開(kāi)了,但web.config文件沒(méi)打開(kāi),需要再次雙擊web.config文件才能打開(kāi)。很是煩人2013-02-02
asp.net生成Excel并導(dǎo)出下載五種實(shí)現(xiàn)方法
有關(guān)Excel下載的文章網(wǎng)上想必有很多,利用閑暇時(shí)間整理了一些有Excel下載方法的文章,接下來(lái)介紹五種實(shí)現(xiàn)Excel下載的方法,感興趣的朋友可以了解下,或許對(duì)你學(xué)習(xí)Excel下載有所幫助2013-02-02

