如何通过2sxc应用C# Razor模板输出DNN页面替代DDR Menu实现自定义菜单
可行性确认
该需求完全可以实现。2sxc原生支持调用DNN核心页面API读取页面数据,同时支持自定义内容类型存储非DNN页面的巨型菜单内容(比如宣传卡片、分类导航、产品入口等),可以完全覆盖DDR Menu的原有能力,还能实现更高自由度的菜单定制。
具体操作步骤
- 第一步:创建2sxc应用与自定义内容类型
新建名为「CustomNavigation」的2sxc应用,新增两个自定义内容类型:- MenuCustomItem:存储非DNN页面的自定义菜单项,字段包含:项名称、跳转链接、打开方式、上级项ID、排序值、是否显示、关联媒体资源(用于巨型菜单的图片/banner)
- MegaMenuBlock:存储巨型菜单的自定义区块内容,字段包含:关联父菜单ID、区块标题、区块内容、排序值、是否启用
- 第二步:配置DNN接口调用权限
在2sxc应用的代码配置中启用DNN核心API调用权限,确保Razor模板可以正常读取DotNetNuke.Entities.Tabs命名空间下的页面数据 - 第三步:对齐DDR Menu渲染逻辑
参照DDR Menu默认过滤规则,过滤已删除、隐藏、权限不足的页面,同时支持按页面层级渲染嵌套菜单结构 - 第四步:合并自定义内容与原生页面数据
渲染前将DNN原生页面数据和自定义菜单项按层级、排序值合并,支持将自定义项插入到任意菜单层级
示例C# Razor代码
@inherits ToSic.Sxc.Dnn.RazorComponent @using DotNetNuke.Entities.Tabs; @using DotNetNuke.Security.Permissions; @{ // 1. 获取当前门户所有权限可见的有效页面 var portalId = Dnn.Portal.PortalId; var allDnnPages = TabController.Instance.GetTabsByPortal(portalId).AsList() .Where(t => !t.IsDeleted && t.IsVisible && !t.DisableLink && TabPermissionController.CanViewPage(t)) .OrderBy(t => t.Level).ThenBy(t => t.TabOrder) .ToList(); // 2. 获取2sxc中存储的自定义菜单项、巨型菜单内容 var customMenuItems = AsList(App.Data["MenuCustomItem"]).Where(i => i.Show == true).OrderBy(i => i.Sort).ToList(); var megaMenuBlocks = AsList(App.Data["MegaMenuBlock"]).Where(b => b.Enable == true).OrderBy(b => b.Sort).ToList(); // 3. 递归渲染菜单层级的方法 Func<int, IHtmlString> RenderMenuLevel = null; RenderMenuLevel = (parentId) => { // 合并当前层级的DNN页面与自定义菜单项 var levelItems = allDnnPages.Where(p => p.ParentId == parentId).Cast<dynamic>() .Concat(customMenuItems.Where(c => c.ParentId == parentId)) .OrderBy(i => i.Sort ?? i.TabOrder) .ToList(); if (!levelItems.Any()) return new HtmlString(""); var html = new StringBuilder($"<ul class=\"nav-level-{parentId}\">"); foreach (var item in levelItems) { var isActive = item.TabID == Dnn.Tab.TabID; var itemUrl = item.Url ?? DotNetNuke.Common.Globals.NavigateURL(item.TabID); var itemTarget = item.Target ?? "_self"; var hasMegaMenu = megaMenuBlocks.Any(b => b.RelatedParentMenuId == (item.TabID ?? item.Id)); html.Append($"<li class=\"nav-item {(isActive ? "active" : "")} {(hasMegaMenu ? "has-mega-menu" : "")}\">"); html.Append($"<a href=\"{itemUrl}\" target=\"{itemTarget}\">{item.MenuName ?? item.TabName}</a>"); // 渲染关联的巨型菜单内容 if (hasMegaMenu) { var blocks = megaMenuBlocks.Where(b => b.RelatedParentMenuId == (item.TabID ?? item.Id)).ToList(); html.Append("<div class=\"mega-menu\">"); foreach (var block in blocks) { html.Append("<div class=\"mega-menu-block\">"); if (!string.IsNullOrEmpty(block.Title)) html.Append($"<h4>{block.Title}</h4>"); html.Append(block.Content); if (block.Image != null) html.Append($"<img src=\"{block.Image.Url}\" alt=\"{block.Title}\">"); html.Append("</div>"); } html.Append("</div>"); } // 递归渲染子菜单 html.Append(RenderMenuLevel(item.TabID ?? item.Id)); html.Append("</li>"); } html.Append("</ul>"); return new HtmlString(html.ToString()); }; } <!-- 最终输出菜单 --> <nav class="custom-main-nav"> @RenderMenuLevel(-1) </nav>
补充说明
- 代码中的CSS类名可以根据你的前端样式需求自由调整,如果需要完全对齐原有DDR Menu的输出结构,直接修改
RenderMenuLevel方法中的HTML输出规则即可 - 自定义菜单项、巨型菜单的内容、排序、显隐都可以直接在2sxc的可视化管理界面操作,无需修改代码
- 权限校验逻辑可以根据业务需求调整,也可以新增对自定义菜单项的权限控制规则
内容的提问来源于stack exchange,提问作者Aaron - Wolf X Machina
相关产品推荐
相关产品推荐

