ZKEACMS for .Net Core深度解析
ZKEACMS 簡介
ZKEACMS.Core 是基于 .Net Core MVC 開發(fā)的開源CMS。ZKEACMS可以讓用戶自由規(guī)劃頁面布局,使用可視化編輯設計“所見即所得”,直接在頁面上進行拖放添加內(nèi)容。
ZKEACMS使用插件式設計,模塊分離,通過橫向擴展來豐富CMS的功能。
響應式設計
ZKEACMS使用Bootstrap3的柵格系統(tǒng)來實現(xiàn)響應式設計,從而實現(xiàn)在不同的設備上都可以正常訪問。同時站在Bootstrap巨人的肩膀上,有豐富的主題資源可以使用。
簡單演示
接下來看看程序設計及原理
項目結(jié)構(gòu)
- EasyFrameWork 底層框架
- ZKEACMS CMS核心
- ZKEACMS.Article 文章插件
- ZKEACMS.Product 產(chǎn)品插件
- ZKEACMS.SectionWidget 模板組件插件
- ZKEACMS.WebHost
原理 - 訪問請求流程
路由在ZKEACMS里面起到了關鍵性的作用,通過路由的優(yōu)先級來決定訪問的流程走向,如果找到匹配的路由,則優(yōu)先走該路由對應的 Controller -> Action -> View,如果沒有匹配的路由,則走路由優(yōu)先權(quán)最低的“全捕捉”路由來處理用戶的請求,最后返回響應。
優(yōu)先級最低的“全捕捉”路由是用來處理用戶自行創(chuàng)建的頁面的。當請求進來時,先去數(shù)據(jù)庫中查找是否存在該頁面,不存在則返回404。找到頁面之后,再找出這個頁面所有的組件、內(nèi)容,然后統(tǒng)一調(diào)用各個組件的“Display"方法來來得到對應的“ViewModel"和視圖"View",最后按照頁面的布局來顯示。
ZKEACMS 請求流程圖
驅(qū)動頁面組件:
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(); } });
頁面呈現(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> }
插件“最關鍵”的類 PluginBase
每一個插件/模塊都必需要一個類繼承PluginBase,作為插件初始化的入口,程序在啟動的時候,會加載這些類并作一些關鍵的初始化工作。
public abstract class PluginBase : ResourceManager, IRouteRegister, IPluginStartup { public abstract IEnumerable<RouteDescriptor> RegistRoute(); //注冊該插件所需要的路由 可返回空 public abstract IEnumerable<AdminMenu> AdminMenu(); //插件在后端提供的菜單 可返回空 public abstract IEnumerable<PermissionDescriptor> RegistPermission(); //注冊插件的權(quán)限 public abstract IEnumerable<Type> WidgetServiceTypes(); //返回該插件中提供的所有組件的類型 public abstract void ConfigureServices(IServiceCollection serviceCollection); //IOC 注冊對應的接口與實現(xiàn) public virtual void InitPlug(); //初始化插件,在程序啟動時調(diào)用該方法 }
具體實現(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òu)成,每個組件都可以包含不同的內(nèi)容(Content),像文字,圖片,視頻等,內(nèi)容由組件決定,呈現(xiàn)方式由組件的模板(View)決定。
關系與呈現(xiàn)方式大致如下圖所示:
實體 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()); } }
實體類里面使用到了元數(shù)據(jù)配置[ViewConfigure(typeof(HtmlWidgetMetaData))],通過簡單的設置來控制表單頁面、列表頁面的顯示。假如設置為文本或下拉框;必填,長度等的驗證。
這里實現(xiàn)方式是向MVC里面添加一個新的ModelMetadataDetailsProviderProvider,這個Provider的作用就是抓取這些元數(shù)據(jù)的配置信息并提交給MVC。
services.AddMvc(option => { option.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider()); })
服務 Service
WidgetService 是數(shù)據(jù)與模板的橋梁,通過Service抓取數(shù)據(jù)并送給頁面模板。 Service 必需繼承自 WidgetService<WidgetBase, CMSDbContext>。如果業(yè)務復雜,則重寫(override)基類的對應方法來實現(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; } } }
視圖實體 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) 用于顯示內(nèi)容。通過了Service收集到了模板所要的“Model”,最后模板把它們顯示出來。
動態(tài)編譯分散的模板
插件的資源都在各自的文件夾下面,默認的視圖引擎(ViewEngine)并不能找到這些視圖并進行編譯。MVC4版本的ZKEACMS是通過重寫了ViewEngine來得以實現(xiàn)。.net core mvc 可以更方便實現(xiàn)了,實現(xiàn)自己的 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); } }
看上面代碼您可能會產(chǎn)生疑惑,為什么要分開發(fā)環(huán)境。這是因為ZKEACMS發(fā)布和開發(fā)的時候的文件夾目錄結(jié)構(gòu)不同造成的。為了方便開發(fā),所以加入了開發(fā)環(huán)境的特別處理。接下來就是注入這個配置:
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, PluginRazorViewEngineOptionsSetup>());
EntityFrameWork
ZKEACMS for .net core 使用EntityFrameWork作為數(shù)據(jù)庫訪問。數(shù)據(jù)庫相關配置 EntityFrameWorkConfigure
public class EntityFrameWorkConfigure : IOnConfiguring { public void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(Easy.Builder.Configuration.GetSection("ConnectionStrings")["DefaultConnection"]); } }
對Entity的配置依然可以直接寫在對應的類或?qū)傩陨?。如果想使?Entity Framework Fluent API,那么請創(chuàng)建一個類,并繼承自 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,定議了許多的變量,像邊距,顏色,背景等等,可以通過簡單的修改變量就能“編譯”出一個自己的主題。
或者也可以直接使用已經(jīng)有的Bootstrap3的主題作為基礎,然后快速創(chuàng)建主題。
最后
關于ZKEACMS還有很多,如果您也感興趣,歡迎加入我們。
ZKEACMS for .net core 就是要讓建網(wǎng)站變得更簡單,快速。頁面的修改與改版也變得更輕松,便捷。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
asp.net通過消息隊列處理高并發(fā)請求(以搶小米手機為例)
這篇文章主要介紹了asp.net通過消息隊列處理高并發(fā)請求(以搶小米手機為例),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-03-03asp.net實現(xiàn)訪問局域網(wǎng)共享目錄下文件的解決方法
這篇文章主要介紹了asp.net實現(xiàn)訪問局域網(wǎng)共享目錄下文件的解決方法,需要的朋友可以參考下2014-07-07asp.net Context.Handler 頁面間傳值方法
很有用的頁面間傳值方法(Context.Handler),使用說明2008-08-08.Net Core WebApi的簡單創(chuàng)建以及使用方法
這篇文章主要給大家介紹了關于.Net Core WebApi的簡單創(chuàng)建以及使用方法的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用.Net Core WebApi具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-09-09.Net?7.0實現(xiàn)支付寶退款和結(jié)果查詢接口
支付寶對 .Net 的支持還是比較充分的,在每個接口文檔中都有關于 C# 語言的示例,這樣就大大降低了對接的難度,很容易上手,這篇文章主要介紹了支付寶退款和結(jié)果查詢接口簡單實現(xiàn)(.Net?7.0),需要的朋友可以參考下2024-07-07asp.net BackgroundWorker之在后臺下載文件
下載文件是常見任務,通常情況下,最好以單獨的線程來運行這項可能很耗時的操作。使用 BackgroundWorker 組件可以用非常少的代碼完成此任務2011-12-12asp.net結(jié)合aspnetpager使用SQL2005的存儲過程分頁
項目中用到了,同事阿春寫了例子,并在實際項目中使用了,記錄下。感謝春哥的無私奉獻。2009-07-07