ZKEACMS for .Net Core 深度解析

来源:http://www.cnblogs.com/seriawei/archive/2017/03/14/6540235.html
-Advertisement-
Play Games

ZKEACMS.Core 是基於 .Net Core MVC 開發的開源CMS。ZKEACMS可以讓用戶自由規劃頁面佈局,使用可視化編輯設計“所見即所得”,直接在頁面上進行拖放添加內容。ZKEACMS使用插件式設計,模塊分離,通過橫向擴展來豐富CMS的功能。 ...


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 就是要讓建網站變得更簡單,快速。頁面的修改與改版也變得更輕鬆,便捷。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • //程式員類(員工類) amespace MyOffice { //程式員類(員工類) public class SE { // 工號 public string ID { get; set; } // 年齡 public int Age { get; set; } ///姓名 public str ...
  • using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; namespace MyOffice{ public class SE { ...
  • 突然意識到文字的重要性,於是開始寫了第一個篇博客,博客目的緊緊為記錄,以便溫故。 同時也希望拋磚能達到引玉的作用,歡迎各位來發表自己的感想與想法,以此達到相互學習促進! 背景: 在做百度地圖電子圍欄的時候,一條圍欄內包含人員的設置是直接通過更新圍欄表(Fence)內FenceUser欄位(數據格式: ...
  • 可以在處理Post方法的Action添加一個特性:[ValidateInput(false)],這樣處理就更加有針對性,提高頁面的安全性。 [HttpPost][ValidateInput(false)]public ActionResult CatalogEdit(Catalog model){r ...
  • 第一篇隨筆,以後會陸續的把剛開始工作時的知識點都記錄下來,畢竟現在用WebForm的不多了~ AutoGenerateColumns MSDN 說明 : 獲取或設置一個值,該值指示是否為數據源中的每個欄位自動創建綁定的欄位。 預設值為true 當AutoGenerateColumns=true時,那 ...
  • using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.... ...
  • RID 是什麼? RID 是運行時標識符的縮寫。 RID 用於標識其中將運行應用程式或資產(即程式集)的目標操作系統。 其外觀類似如下:“ubuntu.14.04-x64”、“win7-x64”、“osx.10.11-x64”。 對於具有本機依賴項的包,它將指定在其中可以還原包的平臺。 Window ...
  • 本人呢還是小實習生一枚,剛一腳踏進社會大母親的懷抱,不想找工作的時候碰到的全是培訓機構。。。 不過還是幸運的進了一家。。。咳咳,國企?!好吧,其實是國企下麵的一個分出來的小公司(正在起步中,算是創業公司)。有人收留是好,可惜,與小白我的專業不是非常的合拍!沒辦法,還得邊學邊做。 天哪嚕,鬼知道我第一 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...