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






4.63/5 (18投票s)
本技巧描述了如何用一种“脏”的方式在 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
,并且有子菜单号 1010
、1020
、1030
等等;当一个菜单号为 1020
的菜单有子菜单时,其子菜单号将是 1021
、1022
等等;当菜单号 1021
有子菜单时,子菜单号将是 10211
、10212
等等;以此类推。这只是一种约定,您可以使用自己的菜单编号方式。
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日:初始版本