65.9K
CodeProject 正在变化。 阅读更多。
Home

用一种“脏”的方式在 ASP.NET MVC 4 中创建动态树状视图菜单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (18投票s)

2016 年 2 月 10 日

CPOL

5分钟阅读

viewsIcon

56241

本技巧描述了如何用一种“脏”的方式在 ASP.NET MVC 4 中创建动态树状视图;创建并渲染 DOM 字符串。

引言

在本文中,我们将使用 ASP.NET MVC 4、C#、Razor 和 SQL Server(我使用的是 Visual Studio 2015 Community Edition 和 SQL Server 2012 Express)来创建动态树状视图菜单。我们将把菜单项(菜单名称、URI 和图标)从我们的 SQL 表 Menu 填充到一个 .NET 数据表中,然后将其处理成 DOM 字符串,最后用 Razor 函数 @Html.Raw() 在 View 代码中渲染。这里的“动态”表示我们可以创建多级树状视图菜单,无论有多少层级。我们将用一种简单粗暴的方式来完成;通过这种方法,我们可以获得多级树状视图菜单,而不是像许多教程中看到的“硬编码”的两三级菜单。

背景

在 Web 应用程序中,尤其是在后台管理应用程序中,我们经常需要创建一个树状视图菜单,可能是在侧边栏或应用程序的顶部。与其在 View 中硬编码树状视图菜单,不如动态创建它,从数据库的 Menu 表中填充菜单项,然后在 View 中渲染。在本文中,我们将使用 ASP.NET MVC 4(使用 C#)的空模板,从头开始演示如何创建动态树状视图菜单。您可以随时修改和简化此源代码。

准备工作

在深入之前,我们在数据库中创建 Menu 表(在您现有的数据库中,或者您可以创建一个新的数据库),表的结构如下所示,Id 列是主键,并设置为自动递增。

这是创建表的脚本

CREATE TABLE [dbo].[Menu] (
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[MenuNumber] [int] NOT NULL,
	[ParentNumber] [int] NULL,
	[MenuName] [varchar](50) NOT NULL,
	[Uri] [varchar](50) NULL,
	[Icon] [varchar](50) NOT NULL,
 CONSTRAINT [PK_Menu] PRIMARY KEY CLUSTERED 
 ( [Id] ASC ) 
  WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
	     IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
	     ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
) ON [PRIMARY]

然后,插入一些值到表中,表看起来会是这样

这是插入 Menu 表以获得上图结果的脚本

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(0, NULL, 'MAIN NAVIGATION', '', 'glyphicon glyphicon-dashboard')

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(1000, 0, 'Dashboard', 'dashboard', 'glyphicon glyphicon-dashboard')

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(2000, 0, 'User', '', 'glyphicon glyphicon-user')

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(3000, 0, 'Setting', '', 'glyphicon glyphicon-cog')

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(2010, 2000, 'Profile', 'user/profile', 'glyphicon glyphicon-sunglasses')

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(2020, 2000, 'My File', 'user/myfile', 'glyphicon glyphicon-folder-open')

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(2021, 2020, 'Document', 'user/myfile/document', 'glyphicon glyphicon-folder-close')

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(2022, 2020, 'Music', 'user/myfile/music', 'glyphicon glyphicon-music')

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(3010, 3000, 'General Setting', 'setting/general', 'glyphicon glyphicon-wrench')

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(3020, 3000, 'Privacy', 'setting/privacy', 'glyphicon glyphicon-lock')

请注意,当一个菜单没有父节点号(NULL)时,该菜单就是所有菜单的(最外层)父菜单。例如,对于子菜单,我们使用菜单号 1000。它有一个父菜单号 0,并且有子菜单号 101010201030 等等;当一个菜单号为 1020 的菜单有子菜单时,其子菜单号将是 10211022 等等;当菜单号 1021 有子菜单时,子菜单号将是 1021110212 等等;以此类推。这只是一种约定,您可以使用自己的菜单编号方式。

Using the Code

我们的数据库中已经有了 Menu 表,也有了数据。现在,我们可以编写源代码了。首先,打开 Visual Studio,然后创建一个新项目,模板选择 Visual C# - ASP.NET Web 应用程序,项目名称命名为 TreeViewMenu。选择 **Empty** 模板,并勾选 MVC 的“添加文件夹和核心引用”。Visual Studio 将为我们创建一个空的 MVC 模板。

在 *Controllers* 文件夹中创建一个控制器,命名为 HomeController。然后,右键单击 ActionResult 方法 Index(),为其添加一个 View,命名为 Index。用以下代码覆盖 View Index.cshtml(位于 ~/Views/Home 文件夹中)。

@{
	Layout = null;
}
<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width" />
	<title>Index</title>

	<link href="~/Contents/css/bootstrap.css" rel="stylesheet" type="text/css" />
</head>
<body>
	<div> 
	   @Html.Raw(ViewBag.DOM_TreeViewMenu)
	</div>
	
	<script src="~/Contents/js/jquery-1.11.3.min.js" type="text/javascript"></script>
	<script src="~/Contents/js/bootstrap.js" type="text/javascript"></script>
</body>
</html>

保存文件。在上面的 cshtml 代码的中间,您可以看到 Razor 函数(或助手)@Html.Raw(),当您在 Web 浏览器中打开它时,这个函数将渲染原始 HTML 代码(DOM)字符串。接下来,我们将在 HomeController 中设置对象 ViewBag.DOM_TreeViewMenu 的值,使其包含树状视图菜单的原始 HTML 代码。

现在,我们可以进入 *Contents* 文件夹,在该文件夹中添加 *css*、*fonts* 和 *js* 文件夹。我们使用标准的 bootstrap、glyphicons 字体和 jQuery(您可以免费下载)。这里,我们只需要获取 glyphicon 来演示带图标的树状视图菜单。在 Solution Explorer 中,您可以看到 *Contents* 文件夹的结构如下所示。

HomeController 中,我们创建三个 private 方法。请看这个骨架代码。

using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Web.Mvc;

namespace TreeViewMenu.Controllers
{
    public class HomeController : Controller
    {
        private SqlConnection conn;
        private SqlDataAdapter da;
        private DataTable dt;

        public ActionResult Index()
        {
            ViewBag.DOM_TreeViewMenu = PopulateMenuDataTable();

            return View();
        }

        private string PopulateMenuDataTable()
        {
            string DOM = "";

            //Do some task here

            return DOM;
        }

        private string GetDOMTreeView(DataTable dt)
        {
            string DOMTreeView = "";

            //Do some task here

            return DOMTreeView;
        }

        private string CreateTreeViewOuterParent(DataTable dt)
        {
            string DOMDataList = "";

            //Do some task here

            return DOMDataList;
        }

        private string CreateTreeViewMenu(DataTable dt, string ParentNumber)
        {
            string DOMDataList = "";

            //Do some task here

            return DOMDataList;
        }
    }
}

我们从方法 PopulateMenuDataTable() 开始。在这里,我们将从 SQL 表 Menu 中填充数据到 DataTable。这是代码。

private string PopulateMenuDataTable()
{
	string DOM = "";

	string sql = @"SELECT MenuNumber, ParentNumber, MenuName, Uri, Icon FROM Menu";
	conn = new SqlConnection(@"Data Source = YOUR_SERVERNAME; 
	                                Initial Catalog = YOUR_DATABASE; 
	                                User ID = sa; Password = YOUR_PASSWORD");
	conn.Open();
	
	da = new SqlDataAdapter(sql, conn);
	da.SelectCommand.CommandTimeout = 10000;
	
	dt = new DataTable();
	da.Fill(dt);

	if (conn.State == ConnectionState.Open)
	{
		conn.Close();
	}
	conn.Dispose();

	DOM = GetDOMTreeView(dt);

	return DOM;
}

然后,为了获取树状视图的 DOM 字符串,我们编写方法 GetDOMTreeView() 和另外两个方法的正文。

private string GetDOMTreeView(DataTable dt)
{
	string DOMTreeView = "";

	DOMTreeView += CreateTreeViewOuterParent(dt);
	DOMTreeView += CreateTreeViewMenu(dt, "0");

	DOMTreeView += "</ul>";
	
	return DOMTreeView;
}

我们从 CreateTreeViewOuterParent() 方法为页眉(最外层父菜单)填充 DOM 字符串。

private string CreateTreeViewOuterParent(DataTable dt)
{
	string DOMDataList = "";

	DataRow[] drs = dt.Select("MenuNumber = 0");

	foreach (DataRow row in drs)
	{
		//row[2], 2 is column number start with 0, which is the MenuName
		DOMDataList = "<ul><li class='header'>" + row[2].ToString() + "</li>";
	}

	return DOMDataList;
}

然后,我们从 Menu 表填充所有 DOM 字符串。我们在 CreateTreeViewMenu() 方法中递归地完成这个操作。您可以查看该方法体,它调用自身以获取 Menu 表中的所有树层级。正如您所见,我们使用 Lambda 表达式来过滤 datatable 的结果,然后将值存储到 DataRow[] 中,这样我们就可以循环 DataRow 并轻松获取值。

private string CreateTreeViewMenu(DataTable dt, string ParentNumber)
{
	string DOMDataList = "";

	string menuNumber = "";
	string menuName = "";
	string uri = "";
	string icon = "";

	DataRow[] drs = dt.Select("ParentNumber = " + ParentNumber);

	foreach (DataRow row in drs)
	{
		menuNumber = row[0].ToString();
		menuName = row[2].ToString();
		uri = row[3].ToString();
		icon = row[4].ToString();

		DOMDataList += "<li class='treeview'>";
		DOMDataList += "<a href='" + uri + "'><i class='" + icon + "'></i><span>  " 
		                + menuName + "</span></a>";

		DataRow[] drschild = dt.Select("ParentNumber = " + menuNumber);
		if (drschild.Count() != 0)
		{
			DOMDataList += "<ul class='treeview-menu'>";
			DOMDataList += CreateTreeViewMenu(dt, menuNumber).Replace
                           ("<li class='treeview'>", "<li>");
			DOMDataList += "</ul></li>";
		}
		else
		{
			DOMDataList += "</li>";
		}
	}
	return DOMDataList;
}

就这些了!调试项目(我使用 Google Chrome),我们将得到类似这张图片的最终结果。如您所见,菜单名称及其图标都已显示;如果您将鼠标悬停在某个菜单上(例如 **My File**),您会发现 URI 也已成功加载到 View 中。

如果我们想添加一个子菜单,比如为菜单 Music 添加一个名为 Dangdut 的子菜单。我们只需要在 Menu 表中插入它,而无需更改我们的源代码。为了演示这一点,请使用此脚本插入菜单 Dangdut

INSERT INTO [dbo].[Menu]([MenuNumber], [ParentNumber], [MenuName], [Uri], [Icon])
VALUES(20221, 2022, 'Dangdut', 'https://en.wikipedia.org/wiki/Dangdut', _
       'glyphicon glyphicon-globe')

现在,结果看起来会是这样

关注点

这是在 ASP.NET MVC 中创建树状视图菜单的一种“脏”方式。与其使用这样的 DOM 字符串,您可以通过使用有用的 List 来操作 DOM 字符串,从而用更简洁的代码减少源代码行数。在实际的 Web 应用程序中,树状视图菜单通常位于主布局 View 中,因此您可以在 Shared 文件夹中创建一个布局视图,其中包含带有树状视图菜单代码的 View。通过对我们的代码进行一些增强,例如使用 bootstrap 和 jQuery,或者使用一些布局模板,我们就可以在 Web 应用程序中创建一个优雅的导航树状视图菜单。

历史

  • 2016年2月10日:初始版本
© . All rights reserved.