MVC5+EF6 入門完整教程13 -- 動態生成多級菜單

来源:http://www.cnblogs.com/miro/archive/2016/05/30/5541086.html
-Advertisement-
Play Games

稍微有一定複雜性的系統,多級菜單都是一個必備組件。 本篇專題講述如何生成動態多級菜單的通用做法。 我們不用任何第三方的組件,完全自己構建靈活通用的多級菜單。 需要達成的效果:容易復用,可以根據model動態產生。 文章提綱 概述要點 && 理論基礎 詳細步驟 一、分析多級目錄的html結構 二、根據 ...


稍微有一定複雜性的系統,多級菜單都是一個必備組件。

本篇專題講述如何生成動態多級菜單的通用做法。

我們不用任何第三方的組件,完全自己構建靈活通用的多級菜單。

需要達成的效果:容易復用,可以根據model動態產生。

 

文章提綱

  • 概述要點 && 理論基礎
  • 詳細步驟

    一、分析多級目錄的html結構

    二、根據html結構構建data model

        三、根據data model動態生成樹形結構

        四、解析樹形結構成html

  • 總結

概述要點 && 理論基礎

要實現動態菜單,只要解決兩個問題:

1. 前端調用

2. 後端可根據model生成菜單

前端調用我們通過自定義html helper的方法;

後端生成菜單我們通過Mvc的TagBuilder來實現。

一、如何自定義html helper?

前面系列文章我們專門介紹過html helpers,例如:
@Html.ActionLink("linkText","someaction","somecontroller",new { id = "123" },null)
生成結果:

<a href="/somecontroller/someaction/123">linkText</a>

 

本次專題我們需要自定義一個html helper用來生成菜單, 命名為GetMenuHtml

View中可以通過 @Html.GetMenuHtml() 實現輸出菜單的html

 

先簡單介紹下如何實現自定義的helper, 具體過程在詳細步驟中再說。

一、定義一個public static的類,在此類中再添加一個public static的方法, 首個參數為 this HtmlHelper helper,該方法就可以像普通的html helper來使用。

二、前端引入類的命名空間:

@using XEngine.Web.Utility.MenuHelper

 

三、在要使用的地方添加:

@Html.SayHi()

 

二、MVC生成html標簽

我們使用TagBuilder

System.Web.Mvc命名空間下TagBuilder的使用詳細介紹:

https://msdn.microsoft.com/en-us/library/system.web.mvc.tagbuilder(v=vs.111).aspx

大家重點關註下方框部分,詳細步驟中可以看到如何使用。

 

 

 

詳細步驟

分成四大步驟

一、分析多級目錄的html結構

首先打開一個樣例,如下圖

對應的html為

大家可以看到,菜單最外面的根節點是一個<li>, 後面跟一個<a>和<ul>, <ul>裡面就是包裹的具體菜單。

具體菜單裡面是<li>, 如果有子菜單通過<li><a><ul>來遞歸

 

二、根據html結構構建data model

根據上面的html結構,我們構建類似如下的SysMenu.

解析菜單時,只需要將相應的欄位填充到html標簽中即可。

[DatabaseGenerated(DatabaseGeneratedOption.None)]

[DisplayName("MenuID")]

public int ID { get; set; }

public int? ParentID { get; set; }

[DisplayName("名稱")]

[StringLength(50)]

public string Name { get; set; }

public string Action { get; set; }

public string Controller { get; set; }

[DisplayName("圖標")]

public string IconImage { get; set; }

public MenuTypeOption MenuType { get; set; }

public List<SysMenu> MenuChildren = new List<SysMenu>();

[DisplayName("描述")]

public string Description { get; set; }

其中 MenuTypeOption表示菜單的種類

三、根據data model生成樹形結構

以一個多級菜單舉例。

這個菜單中每一級對應一個SysMenu.

SysMenu之間有父子關係,通過MenuChildren來實現。

我們建立一個ViewModel,專門存放根菜單(根菜單下麵的菜單可以根據MenuChildren來找到,不需要再專門保存)

public class MenuViewModel<T>

{

public IList<T> MenuItems = new List<T>();

}

先增加幾筆測試數據

 

 

 

 

現在我們就來構建這個菜單的樹形結構

public static MenuViewModel<SysMenu> CreateMenuModel(string menuName)

{

UnitOfWork unitOfWork = new UnitOfWork();

MenuViewModel<SysMenu> model = new MenuViewModel<SysMenu>();

// 1. 根據menuName獲取開始的根菜單

unitOfWork.SysMenuRepository.Get(filter: m => m.Name == menuName).FirstOrDefault();

    if (itemRoot != null)

{

    // 2. 依次添加枝葉菜單

    // 2.1 獲取itemRoot的所有子菜單

IEnumerable<SysMenu> menus = unitOfWork.SysMenuRepository.Get(filter: m => m.ParentID == itemRoot.ID);

// 2.2 對每個子菜單進行遞歸 AddChildNode

foreach (var item in menus)

{

itemRoot.MenuChildren.Add(item);

AddChildNode(item);

}

}

}

 

//遞歸執行:找到menu子成員並添加

public static void AddChildNode(SysMenu menu)

{

UnitOfWork unitOfWork = new UnitOfWork();

var menus = unitOfWork.SysMenuRepository.Get(filter: m => m.ParentID == menu.ID);

foreach (var item in menus)

{

menu.MenuChildren.Add(item);

AddChildNode(item);

}

}

 

 

四、解析樹形結構生成菜單html

第三步組裝好樹形結構後,我們再將菜單解析出來,添加相應的tag , 拼接出菜單的html

我們先定義一個類TagContainer,用來放tag

public class TagContainer

{

public int OrdinalNum;

public string Name;

public TagBuilder Tb;

public TagContainer ParentContainer;

public List<TagContainer> ChildrenContainers = new List<TagContainer>();

 

public TagContainer(ref int Num, TagContainer parent)

{

OrdinalNum = Num++;

ParentContainer = parent;

if (parent!=null)

{

parent.ChildrenContainers.Add(this);

}

}

}

說明:

其中OrdinalNum表示記錄的序號(構建時,每個TagContainer都有個OrdinalNum作為標記,每產生一個li或ul都加1)

Tb是MVC原生的類,包含用於創建 HTML 元素的類和屬性。

 

構建個類BaseHtmlTagEngine,專門用來處理轉換標簽的相關工作

其中_TopTagContainer 為放置根菜單的容器, 從 _TopTagContainer 這個節點開始,會將所有的子成員tag進行填充。

public abstract class BaseHtmlTagEngine<T> where T:IItem<T>

{

protected int _CntNumber = 0;

TagContainer _TopTagContainer;

string _OutString;

protected HtmlHelper _htmlHelper;

 

public BaseHtmlTagEngine(HtmlHelper htmlHelper)

{

_htmlHelper = htmlHelper;

}

 

public TagContainer TopTagContainer

{

get { return _TopTagContainer; }

}

 

        //…其他相關方法,下麵會有詳解

}

說明:上面的 _OutString 就是我們最終解析出來的菜單html

 

具體轉換步驟:

1. 將Model轉換成帶標簽的樹形結構

在BaseHtmlTagEngine添加方法BuildTreeStruct ,將model轉化成帶標簽的結構

public void BuildTreeStruct(MenuViewModel<T> model)

{

_CntNumber = 0;

try

{

// 1.先設置放置根菜單的容器

_TopTagContainer = new TagContainer(ref _CntNumber, null);

 

foreach (T mi in model.MenuItems)

{

BuildTagContainer(mi, _TopTagContainer);

}

}

catch (Exception)

{

 

throw;

}

}

 

通過 BuildTagContainer 添加tag

為了代碼結構更加清晰(另外也可以復用構建其他),我們再添加一個新的類HtmlBuilder繼承BaseHtmlTagEngine, 具體的實現方法 BuildTagContainer 及相關的其他方法都放在這個類中

protected void BuildTagContainer(SysMenu item, TagContainer parent)

{

TagContainer tc = FillTag(item, parent);

 

foreach (SysMenu mmi in item.GetChildren())

{

BuildTagContainer(mmi, tc);

}

}

TagContainer FillTag(SysMenu item, TagContainer tc_parent)

{

//先把本身的菜單項加上(每一個項都以li開始)

         TagContainer li_tc = new TagContainer(ref _CntNumber,tc_parent);

li_tc.Name = item.Name;

li_tc.Tb = AddItem(item); //li tag

if (HasChildren(item))

{

TagContainer ui_container = new TagContainer(ref _CntNumber, li_tc);

ui_container.Name = "**";

ui_container.Tb = Add_UL_Tag();

return ui_container;

}

return li_tc;

}

TagBuilder Add_UL_Tag()

{

TagBuilder ul_tag = new TagBuilder("ul");

ul_tag.AddCssClass("dropdown-menu");

return ul_tag;

}

AddItem 將具體的一個菜單項轉化成具有標簽的完整菜單項

(即li 及 li包含的子tag 及 相關的標簽屬性(如鏈接地址)、樣式等)

最終返回的TagBuilder如果轉化成字元串應該類似如下形式:

{<li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="/XEngine/"><img class="xxx" src="xxx"></img>MenuTest<b class="caret"></b></a></li>}

 

AddItem 具體實現

TagBuilder AddItem(SysMenu mi)

{

var li_tag = new TagBuilder("li");

var a_tag = new TagBuilder("a");

var b_tag = new TagBuilder("b");

var image_tag = new TagBuilder("img");

 

if (mi.IconImage != null)

{

string path = "Images/" + mi.IconImage;

image_tag.MergeAttribute("src", path);

}

 

b_tag.AddCssClass("caret");

 

var contentUrl = GenerateContentUrlFromHttpContext(_htmlHelper);

string a_href = GenerateUrlForMenuItem(mi, contentUrl);

 

a_tag.Attributes.Add("href", a_href);

 

if (mi.MenuType == MenuTypeOption.Top)

{

li_tag.AddCssClass("dropdown");

a_tag.MergeAttribute("data-toggle", "dropdown");

a_tag.AddCssClass("dropdown-toggle");

}

else

{

li_tag.AddCssClass("dropdown-submenu");

}

 

a_tag.InnerHtml += image_tag.ToString();

a_tag.InnerHtml += mi.Name;

 

if (HasChildren(mi))

{

a_tag.InnerHtml += b_tag.ToString();

}

 

li_tag.InnerHtml = a_tag.ToString();

return li_tag;

}

2. 解析上面的樹形結構並轉化成html

首先看下最終生成菜單的結構(做了適當簡化):

<li class="dropdown">

<a href="xx" data-toggle="dropdown" class="dropdown-toggle">MenuTest </a>

<ul class="dropdown-menu">

<li class="dropdown-submenu">

<a href="xx">Level 1a</a>

<ul class="dropdown-menu">

<li> <a href="xx">Level 2</a> </li>

</ul>

</li>

<li>

<a href="/XEngine/">Level 1b</a>

</li>

</ul>

</li>

對照效果圖 :

 

解析演算法:

 

一直遞歸這些步驟, 直到移到根節點。這個根節點包含所有的HTML

 

 

示例菜單開始的幾個過程舉例:

1. 獲取葉節點 Level 2和 Level 1b, 取第一個葉節點 Level 2

2. 把Level 2的Html加入到上一級的InnerHtml中去,

_OutString設置為上一級的容器的Html, 即

<ul class="dropdown-menu">

<li> <a href="xx">Level 2</a> </li>

</ul>

此為一個完整過程。

 

向上提升一級:tc = tc.ParentContainer; 遞歸上面的過程

_OutString設置為上一級的容器的Html, 即

<li class="dropdown-submenu">

<a href="xx">Level 1a</a>

<ul class="dropdown-menu">

<li> <a href="xx">Level 2</a> </li>

</ul>

</li>

向上提升一級:tc = tc.ParentContainer; 遞歸上面的過程

_OutString設置為上一級的容器的Html, 即

<ul class="dropdown-menu">

<li class="dropdown-submenu">

<a href="xx">Level 1a</a>

<ul class="dropdown-menu">

<li> <a href="xx">Level 2</a> </li>

</ul>

</li>

</ul>

註意此時 Level 1a是有兄弟節點Level 1b的,遞歸過程中碰到有兄弟節點時我們就要將本身從完整的樹形結構移除掉並停止遞歸:

tc.ParentContainer.ChildrenContainers.Remove(tc);

再重新掃描這棵樹(從第一步開始再執行),依次將剩餘的葉節點分支往上一直添加到_OutString中去。

這樣一直將所有的葉節點分支都添加完後,當tc.ParentContainer為null即已經到了根節點時,處理過程結束,直接輸出_OutString到前端就可以了。

具體代碼:

public string Build()

{

try

{

while (true)

{

// 獲取第一個葉節點

TagContainer tc = GetNoChildNode(_TopTagContainer);

bool PrcComplete = false;

Levelup(tc, ref PrcComplete);

if (PrcComplete)

{

break;

}

}

}

catch (Exception)

{

throw;

}

return _OutString;

}

 

 

遞歸執行移除分支掃描樹

private void Levelup(TagContainer tc, ref bool ProcessingComplete)

{

while(tc!=null)

{

if (tc.ParentContainer!=null)

{

if (tc.ParentContainer.Tb!=null)

{

tc.ParentContainer.Tb.InnerHtml += tc.Tb.ToString();

_OutString = tc.ParentContainer.Tb.ToString();

}

else

{

ProcessingComplete = true;

break; //dummy or invalid container

}

if (tc.ParentContainer.ChildrenContainers.Count>1)

{

tc.ParentContainer.ChildrenContainers.Remove(tc);

break;

}

tc = tc.ParentContainer; // moving up the tree

}

else

{

ProcessingComplete = true;

break;

}

}

}

 

前端使用:

1. 加上命名空間

@using XEngine.Web.Utility.MenuHelper

2. 添加helper

@Html.Raw(Html.GetMenuHtml("MenuTest"))

註意原生的helper返回類型是MvcHtmlString 類型的,表示不應再次進行編碼的 HTML 編碼的字元串。

而我們返回的類型是string , 因此需要加上@Html.Raw()否則就不能正確顯示。

 

總結

本篇主要講了兩個知識點 : 如何自定義html helper和 TagBuilder的使用。

自定義的html helper 第一個參數必須為 this HtmlHelper類型。

至於生成html tag,使用MVC原生的TagBuilder比較方便,註意方法的返回值要為MvcHtmlString ,如果返回值定義為String,返回的字元竄會被轉義,為了防止轉義我們可以用@Html.Raw來接收。當然你也可以不用TagBuilder純手工拼接。

這個示例只要稍加擴展就可以很靈活的實現各種實際項目需求。

例如可以和許可權結合起來,先過濾一遍許可權,動態生成有許可權的看到的菜單等。

歡迎大家多多評論,祝學習進步:)

P.S.

示例中前端直接在_Layout.cshtml中使用。

後端菜單相關的程式結構:

另外公司研發部招聘工程師2名(R語言方向 & .NET開發方向),主要研發數據可視化相關新產品,有興趣的可以博客園短消息聯繫我。

base 在蘇州高新區

完整目錄:


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

-Advertisement-
Play Games
更多相關文章
  • 一般,我們看到術語“索引”和“鍵”交換使用,但實際上這兩個是不同的。索引是存儲在資料庫中的一個物理結構,鍵純粹是一個邏輯概念。鍵代表創建來實施業務規則的完整性約束。索引和鍵的混淆通常是由於資料庫使用索引來實施完整性約束。 接下來我們看看資料庫中的主鍵約束、唯一鍵約束和唯一索引的區別。 SQL> se ...
  • MongoDB是一個開源的文檔資料庫,支持高性能、高可用性、自動縮放。 在MongoDB中,一條記錄就是一個文檔,是由欄位和值對構成一個數據結構,類似於JSON對象。欄位的值可以包括其他文檔、數組和文檔的數組。 數據結構如下所示: ...
  • 腳本最好都放在/usr/local/sbin中 腳本的執行 sh -x 腳本.sh -x可以查看執行過程 1在腳本中使用變數 使用變數的時候,需要使用$符號: #!/bin/bash ##把命令賦值為變數,需要使用反引號 d=`date +"%H:%M:%S"` echo "The script b ...
  • grep是UNIX和LINUX中使用最廣泛的命令之一。grep允許對文本文件進行模式查找。如果找到匹配模式, grep列印包含模式的所有行。grep支持基本正則表達式,也支持其擴展集。grep有三種變形,即: grep:標準grep命令,這裡主要討論此格式; Egrep:等同於grep -E,擴展g ...
  • 算術運算符 + - * / % 表示加減乘除和取餘運算+= -= *= /= 同 C 語言中的含義 位操作符 > >>= 表示位左右移一位操作& &= | |= 表示按位與、位或操作~ ! 表示非操作^ ^= 表示異或操作 關係運算符 = == != 表示大於、小於、大於等於、小於等於、等於、不等於 ...
  • OSI 七層模型通過七個層次化的結構模型使不同的系統不同的網路之間實現可靠的通訊,因此其最主要的功能就是幫助不同類型的主機實現數據傳輸 。 OSI 七層模型通過七個層次化的結構模型使不同的系統不同的網路之間實現可靠的通訊,因此其最主要的功能就是幫助不同類型的主機實現數據傳輸 。 完成中繼功能的節點通 ...
  • 上一篇我們已經可以獲取各種FileHandler的實例和對應的元數據。本篇,我們做一個稍微完整的文件管理器。 1、修改介面IFileHandler,傳入文件名 2、修改具體的FileHandler。 3、修改主函數 運行結果: 可以看到,對每一個具體的文件,均找到了正確的處理實例進行處理。avi文件 ...
  • 首先我們都知道引用類型預設值都是null,而值類型的預設值都有非null。為什麼引用類型可以為空?因為引用類型變數都是保存一個對象的地址引用(就像一個url對應一個頁面),而引用類型值為null的時候是變數值指向了一個空引用(如同一個空的url)那為什麼值不能有空值呢?其實很簡單,因為如int值範圍... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...