C#无限极分类树-创建-排序-读取 用Asp.Net Core+EF实现
今天做一个管理后台菜单,想着要用无限极分类,记得园子里还是什么地方见过这种写法,可今天找了半天也没找到,没办法静下心来自己写了:
首先创建节点类(我给它取名:AdminUserTree):
1 /// <summary> 2 /// 无限极节点类 3 /// </summary> 4 public class AdminUserTree 5 { 6 /// <summary> 7 /// 节点信息 8 /// </summary> 9 public int NodeID { get; set; } 10 /// <summary> 11 /// 节点名称 12 /// </summary> 13 public string NodeName { get; set; } 14 /// <summary> 15 /// 父节点ID 16 /// </summary> 17 public int ParentID { get; set; } 18 /// <summary> 19 /// 对应的链接地址 20 /// </summary> 21 public string Url { get; set; } 22 public int? PermissionID { get; set; } 23 public int? OrderID { get; set; } 24 public string Location { get; set; } 25 public string comment { get; set; } 26 public string ImageUrl { get; set; } 27 /// <summary> 28 /// 层级 29 /// </summary> 30 public int level { get; set; } 31 /// <summary> 32 /// 子节点数目(很重要) 33 /// </summary> 34 public int childNumberl { get; set; } 35 36 /// <summary> 37 /// 子节点 (子节点是一个List)这种用法叫什么? 38 /// </summary> 39 public List<AdminUserTree> childNode { get; set; } 40 }
为无限极分类填充数据,由于考虑到示来管理后台每个页面都会调用到,这里我为控制器创建了一个基类方法
1 /// <summary> 2 /// 管理页面基类(MVC Controller) 3 /// </summary> 4 public class AdminBase: Controller 5 { 6 /// <summary> 7 /// EF数据访问配置 8 /// </summary> 9 private readonly ApplicationDbContext _basecontext; 10 11 /// <summary> 12 /// 管理菜单 这里是基数,声明为属性以便控制器里面可以用到 13 /// </summary> 14 public AdminUserTree leftMenu { get; set; } 15 16 17 public AdminBase(ApplicationDbContext context) 18 { 19 _basecontext = context; 20 //初始化无限极分类管理菜单 21 buildtree(); 22 } 23 24 /// <summary> 25 /// 建立无限极节点树-管理菜单 26 /// </summary> 27 public void buildtree() 28 { 29 AdminUserTree result = new AdminUserTree(); 30 //初始化一个节点做为根节点 31 result.NodeID = 0; 32 result.NodeName= "管理员菜单"; 33 result.Url = ""; 34 result.ParentID = -1; 35 result.Location = ""; 36 result.OrderID = 0; 37 result.comment = ""; 38 result.ImageUrl = ""; 39 result.PermissionID = 0; 40 result.level = 0; 41 result.childNumberl = 0; 42 //把根节点传递给递归方法去创建子节点 43 result.childNode=BuildMenuTree(result, -1); 44 leftMenu = result; 45 } 46 47 /// <summary> 48 /// 递归创建子节点方法 49 /// </summary> 50 /// <param name="node">要为其分配子节点的父级节点</param> 51 /// <param name="levelID">层级关系</param> 52 /// <returns></returns> 53 protected List<AdminUserTree> BuildMenuTree(AdminUserTree node, int levelID) 54 { 55 var listtree = _basecontext.Admintree; 56 57 //从数据库中取出node节点的全部子节点 条件:m.ParentID==node.NodeID 58 List<AdminUserTree> lt = listtree.Where(m => m.ParentID==node.NodeID ) 59 .Select(m=>new AdminUserTree() { 60 NodeID =m.NodeID 61 ,NodeName=m.NodeName 62 ,Url=m.Url 63 ,ParentID=m.ParentID 64 ,Location=m.Location 65 ,OrderID=m.OrderID 66 ,comment=m.comment 67 ,ImageUrl=m.ImageUrl 68 ,PermissionID=m.PermissionID}) 69 .ToList(); 70 71 if (lt != null) 72 { 73 //节点深度 74 node.level = levelID + 1; 75 //子节点数量,便于前端递归输出时调用 76 node.childNumberl = lt.Count; 77 for (int i = 0; i < lt.Count; i++) 78 { 79 //递归调用创建子节点 80 lt[i].childNode = BuildMenuTree(lt[i], node.level); 81 } 82 return lt; 83 84 } 85 else { 86 return null; 87 } 88 89 } 90 91 }
控制器(Controller)继承及调用代码:
1 public class AdminTreeController : AdminBase 2 { 3 private readonly ApplicationDbContext _context; 4 5 6 public AdminTreeController(ApplicationDbContext context):base(context) 7 { 8 _context = context; 9 } 10 11 // GET: AdminTree 12 public async Task<IActionResult> Index(int id=1) 13 { 14 var treelist = _context.Admintree; 15 var pageOption = new WeiPagerOption 16 { 17 CurrentPage = id, 18 PageSize = 15, 19 Total = await treelist.CountAsync(), 20 RouteUrl = "/Admintree/Index", 21 pageNumStep = 5 22 }; 23 24 //分页参数 25 ViewBag.PagerOption = pageOption; 26 27 //无限极分类菜单绑定在这里 28 ViewBag.mainMenu = leftMenu; 29 30 //返回主要数据 31 return View(await treelist.OrderByDescending(b => b.OrderID).Skip((pageOption.CurrentPage - 1) * pageOption.PageSize).Take(pageOption.PageSize).ToListAsync()); 32 } 33 }
View层代码:
1 @model IEnumerable<Hxwei.WebWQSF.Models.AdminTreeModel> 2 @using Hxwei.WebWQSF; 3 @using Hxwei.WebWQSF.Controllers; 4 @using System.Text; 5 @{ 6 ViewData["Title"] = "菜单管理"; 7 } 8 @functions 9 { 10 public string getAdminMenu(AdminUserTree node) 11 { 12 StringBuilder sb = new StringBuilder(); 13 14 List<AdminUserTree> ls = node.childNode; 15 if(ls.Count>0) 16 { 17 //遍历每个子节点以输出,这里用到了排序ls.OrderBy(m => m.OrderID) 18 foreach (var r in ls.OrderBy(m => m.OrderID)) 19 { 20 if (r.childNumberl > 0) 21 { 22 //当存在子菜单时的方法,这里会有递归调用 23 sb.Append("<div class=\"btn-group\">"); 24 sb.Append("<button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\">"); 25 sb.Append(r.NodeName); 26 sb.Append("<span class=\"caret\"></span>"); 27 sb.Append("</button>"); 28 sb.Append("<ul class=\"dropdown-menu\" role=\"menu\">"); 29 //递归调用 30 sb.Append(getAdminMenu(r)); 31 sb.Append("</ul>"); 32 sb.Append("</div>"); 33 34 } 35 else 36 { 37 //当不存在子菜单时输出 38 string ntext = string.Format("<li><a href=\"{0}\">{1}</a></li>",r.Url,r.NodeName); 39 sb.Append(ntext); 40 41 } 42 } 43 44 } 45 46 47 return sb.ToString(); 48 } 49 } 50 <div class="row"> 51 <div class="col-md-3 navbar-inverse"> 52 <div class="btn-group-vertical col-md-10"> 53 54 <button type="button" class="btn btn-default">@ViewBag.mainMenu.NodeName</button> 55 @Html.Raw(getAdminMenu(ViewBag.mainMenu)); 56 57 </div> 58 59 </div> 60 <div class="col-md-9"> 61 <h2>Index</h2> 62 63 <p> 64 <a asp-action="Create">Create New</a> 65 </p> 66 <table class="table"> 67 <thead> 68 <tr> 69 <th> 70 @Html.DisplayNameFor(model => model.NodeName) 71 </th> 72 <th> 73 @Html.DisplayNameFor(model => model.ParentPath) 74 </th> 75 <th> 76 @Html.DisplayNameFor(model => model.OrderID) 77 </th> 78 <th> 79 @Html.DisplayNameFor(model => model.Url) 80 </th> 81 <th></th> 82 </tr> 83 </thead> 84 <tbody> 85 @foreach (var item in Model) 86 { 87 <tr> 88 <td> 89 @Html.DisplayFor(modelItem => item.NodeName) 90 </td> 91 <td> 92 @Html.DisplayFor(modelItem => item.ParentPath) 93 </td> 94 <td> 95 @Html.DisplayFor(modelItem => item.OrderID) 96 </td> 97 <td> 98 @Html.DisplayFor(modelItem => item.Url) 99 </td> 100 <td> 101 <a asp-action="Edit" asp-route-id="@item.NodeID">Edit</a> | 102 <a asp-action="Details" asp-route-id="@item.NodeID">Details</a> | 103 <a asp-action="Delete" asp-route-id="@item.NodeID">Delete</a> 104 </td> 105 </tr> 106 } 107 </tbody> 108 <tr><td colspan="5" align="center"><pager pager-option="ViewBag.PagerOption as WeiPagerOption"></pager></td></tr> 109 </table> 110 </div> 111 </div>
最后生成的菜单浏览器展示效果如下:
写了很久简单三层,最近决定用一下ASP.NET MVC,最近刚了解了一下ASP.NET MVC,目前最新的算是ASP.NET Core MVC,这个例子就是刚刚安装了VS2017 RC后用ASP.NET Core MVC来实现的。学习阶段希望与各位大神共勉,有不足的地方请多多指教!谢谢!
在做完这个类子后,我觉得后续还有可以优化的地方,我是从这几个方面考虑的,希望高手给予指点:
1.这里在构建无限极分类树时我是多次调用数据库查询,如果数据量小的话想着是把数据一次取出然后传递后递归方法进行操作;由于用了EF,对于EF我也是个新手,只是刚刚会用,不知道EF本身会不会对这种类型的操作进行优化及数据缓存。
2.第二个方面是在无限极分类树数据真充好后由于每个管理页面都要调用这个树的数据,考虑要对其进行缓存,如何缓存是我下一步要考虑的方法;
3.同时每个节点的权限不同,由于每个用户角色的不同权限所能调用的菜单功能也不一致,这就存在了是为每一个用户都缓存一棵树还是全局共享一棵树的问题,显然前者是不科学的,应该是全员共享一棵树的数据,只需要在View层显示时加以权限判断就可以了。这也是我在下一步要考虑的。
后续会先解决以上提到第2和第3方面的问题,等我写好后再把代码分享出来,大家一起讨论!