首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> 關於ASP.NET

ASP.NET 動態多層級選單實作:TreeView 控制項搭配 XML 資料源完整指南

     編輯:關於ASP.NET

需求場景

企業應用常需呈現不定層級的功能選單(如:商品分類、組織架構、知識庫目錄),資料來源為資料庫遞迴關係表(ParentID 自關聯),需支援動態載入、展開/摺疊、權限過濾、連結導航。

資料庫結構設計

CREATE TABLE MenuItems (
    MenuId       INT IDENTITY PRIMARY KEY,
    ParentId     INT NULL REFERENCES MenuItems(MenuId),
    Text         NVARCHAR(100) NOT NULL,
    NavigateUrl  NVARCHAR(500),
    Target       NVARCHAR(50) DEFAULT '_self',
    SortOrder    INT DEFAULT 0,
    IconCssClass NVARCHAR(100),
    IsVisible    BIT DEFAULT 1,
    Roles        NVARCHAR(500)  -- 逗號分隔角色代碼,空白=公開
);

遞迴查詢與樹狀建構

CTE 遞迴查詢(SQL Server 2005+)

WITH MenuTree AS (
    SELECT MenuId, ParentId, Text, NavigateUrl, Target, SortOrder, IconCssClass, 0 AS Level
    FROM MenuItems WHERE ParentId IS NULL AND IsVisible = 1
    UNION ALL
    SELECT m.MenuId, m.ParentId, m.Text, m.NavigateUrl, m.Target, m.SortOrder, m.IconCssClass, mt.Level + 1
    FROM MenuItems m INNER JOIN MenuTree mt ON m.ParentId = mt.MenuId
    WHERE m.IsVisible = 1
)
SELECT * FROM MenuTree ORDER BY Level, SortOrder;

C# 建構樹狀物件

public class MenuNode {
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Text { get; set; }
    public string NavigateUrl { get; set; }
    public string Target { get; set; } = "_self";
    public string IconCssClass { get; set; }
    public List Children { get; } = new List();
}

public List BuildTree(IEnumerable flatList) {
    var lookup = flatList.ToLookup(n => n.ParentId ?? 0);
    Action attachChildren = null;
    attachChildren = node => {
        node.Children.AddRange(lookup[node.Id].OrderBy(c => c.SortOrder));
        foreach (var child in node.Children) attachChildren(child);
    };
    var roots = lookup[0].OrderBy(r => r.SortOrder).ToList();
    roots.ForEach(attachChildren);
    return roots;
}

TreeView 綁定三種方式

方式一:宣告式 SiteMapDataSource(靜態結構)

<asp:TreeView ID="tvMenu" runat="server" DataSourceID="sitemapDs">
    <DataBindings>
        <asp:TreeNodeBinding DataMember="MenuItem" TextField="Title" NavigateUrlField="Url" TargetField="Target" />
    </DataBindings>
</asp:TreeView>
<asp:SiteMapDataSource ID="sitemapDs" runat="server" ShowStartingNode="False" />

方式二:後端動態建構 TreeNode(完全控制)

private void BindTreeView(List roots) {
    tvMenu.Nodes.Clear();
    foreach (var root in roots) {
        tvMenu.Nodes.Add(CreateTreeNode(root));
    }
}

private TreeNode CreateTreeNode(MenuNode node) {
    var tn = new TreeNode {
        Text = node.Text,
        Value = node.Id.ToString(),
        NavigateUrl = node.NavigateUrl,
        Target = node.Target,
        ToolTip = node.Text,
        ImageUrl = string.IsNullOrEmpty(node.IconCssClass) ? null : ResolveIconUrl(node.IconCssClass),
        SelectAction = string.IsNullOrEmpty(node.NavigateUrl) ? TreeNodeSelectAction.Expand : TreeNodeSelectAction.Select
    };
    foreach (var child in node.Children) {
        tn.ChildNodes.Add(CreateTreeNode(child));
    }
    return tn;
}

方式三:XML 資料檔 + TreeNodeSrc(舊版相容)

如原文所示,TreeNodeSrc="menu.xml" 可載入外部 XML,適合靜態選單、跨專案共用結構。XML 結構:

<TREENODES>
  <treenode text="產品管理" NavigateUrl="~/Product/List.aspx" Target="main">
    <treenode text="產品清單" NavigateUrl="~/Product/List.aspx" />
    <treenode text="新增產品" NavigateUrl="~/Product/Create.aspx" />
  </treenode>
  <treenode text="訂單管理" NavigateUrl="~/Order/List.aspx" Target="main">
    <treenode text="訂單清單" NavigateUrl="~/Order/List.aspx" />
  </treenode>
</TREENODES>

效能優化:延遲載入

大型選單(>500 節點)一次載入會導致 ViewState 膨脹、頁面渲染緩慢。啟用 PopulateOnDemand="true" + TreeNodePopulate 事件:

<asp:TreeView ID="tvMenu" runat="server" PopulateOnDemand="true" OnTreeNodePopulate="tvMenu_TreeNodePopulate"></asp:TreeView>

protected void tvMenu_TreeNodePopulate(object sender, TreeNodeEventArgs e) {
    int parentId = int.Parse(e.Node.Value);
    var children = MenuService.GetChildren(parentId, CurrentUser.Roles);
    foreach (var child in children) {
        var tn = new TreeNode(child.Text, child.Id.ToString()) {
            NavigateUrl = child.NavigateUrl,
            Target = child.Target,
            PopulateOnDemand = child.HasChildren
        };
        e.Node.ChildNodes.Add(tn);
    }
    e.Node.Populated = true;
}

權限過濾整合

public IEnumerable GetVisibleMenus(IPrincipal user) {
    var all = MenuRepository.GetAll();
    return all.Where(m => m.IsVisible && (string.IsNullOrEmpty(m.Roles) || m.Roles.Split(',').Any(r => user.IsInRole(r.Trim()))));
}

現代化替代:前端樹狀元件 + Web API

  • jQuery:jsTree、zTree、Fancytree
  • Vue/React:Element Plus Tree、Ant Design Tree、React Arborist
  • Blazor:MudBlazor TreeView、Radzen Tree

後端提供 GET /api/menus/tree 回傳巢狀 JSON,前端元件負責渲染、展開、搜尋、拖曳排序,完全分離關注點。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved