MVC 6动态导航菜单从数据库
表的内容 目标介绍组件使用创建项目迁移数据服务导航菜单下一个结论历史 目标 几年前,我不得不从数据库加载导航菜单并使用web forms创建菜单控件,所以从数据库加载菜单数据的主要思想是根据用户角色进行过滤。最后,我们将按角色过滤数据。这里我们使用ASP。NET Core 2.2 MVC应用程序。 介绍 我在MVC 6。net Core中遇到了这个需求,从数据库中动态生成一个基于角色的导航菜单,这样它就可以用来探索网站和管理面板,用于管理分配角色,权限和其他应用程序的维护。在这个系统中,角色的数量是有限的,因此基于角色的授权是合适的。 组件使用 下面是构建和测试提供的演示代码所需的组件。 下载最新的Visual Studio 2019社区版如果你没有专业版或企业版,我使用的是SQL server Developer edition 17.9.1,你可以从这个链接下载。 创建Web项目 在Visual Studio 2019中创建您的Web应用程序。 选择语言为c#,项目类型为Web,然后选择第一个模板,ASP。NET Core Web应用程序并单击Next。 提供项目名称并选择物理路径,然后单击Create。 选择Web应用程序(模型-视图-控制器),然后单击右边Authentication下面的Change按钮。在此之后,选择单个用户帐户,ok关闭弹出,然后创建。 现在项目已经设置好,可以运行了,但是我们还没有基于我们的模型创建任何数据库,所以首先,我们需要更改appsettings中的连接字符串。json文件。我将使用本地主机作为我的服务器与Windows身份验证,下面是我的连接字符串。 隐藏,复制Code
"DefaultConnection": "Server=localhost;Database=DynamicMenu; Trusted_Connection=True;MultipleActiveResultSets=true"
但如果我们在这个级别创建数据库,我们将只有标识表,如下所示: 但在本例中,我们还需要两个表,我们将首先使用代码通过定义它们的实体来创建它们,然后将它们添加到上下文类中。 隐藏,收缩,复制Code
[Table(name: "AspNetRoleMenuPermission")] public class RoleMenuPermission { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } [ForeignKey("ApplicationRole")] public string RoleId { get; set; } [ForeignKey("NavigationMenu")] public Guid NavigationMenuId { get; set; } public NavigationMenu NavigationMenu { get; set; } } [Table(name: "AspNetNavigationMenu")] public class NavigationMenu { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } public string Name { get; set; } [ForeignKey("ParentNavigationMenu")] public Guid? ParentMenuId { get; set; } public virtual NavigationMenu ParentNavigationMenu { get; set; } public string ControllerName { get; set; } public string ActionName { get; set; } [NotMapped] public bool Permitted { get; set; } } public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<RoleMenuPermission> RoleMenuPermission { get; set; } public DbSet<NavigationMenu> NavigationMenu { get; set; } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); } }
迁移 现在我们需要运行迁移,然后更新数据库,Enable-Migrations命令已经过时了,所以我们需要从migrations文件夹中删除所有内容,然后运行add migration命令。 隐藏,复制Code
add-migration InitialVersion
它会在Migrations文件夹中创建一些文件,然后我们需要运行update-database命令,如果你的连接字符串是正确的,那么它会像下面这样创建你的数据库: 关于种子数据的更多细节,你可以查看我的另一篇文章: 种子数据MVC 6。net核心应用程序 对于当前场景,我们的种子将拥有所有导航菜单项、用户、角色和权限。所以它会有点复杂。 现在我们已经有了包含所有实体的数据库,所以让我们在开发环境中运行应用程序,它将在数据库中插入种子数据。 数据服务 我们将创建一个数据服务来与数据库通信,它非常简单,它有一个主要函数GetMenuItemsAsync,该函数在按角色过滤后返回导航菜单视图模型。 隐藏,收缩,复制Code
public class DataAccessService : IDataAccessService { private readonly ApplicationDbContext _context; public DataAccessService(ApplicationDbContext context) { _context = context; } public async Task<List<NavigationMenuViewModel>> GetMenuItemsAsync(ClaimsPrincipal principal) { var isAuthenticated = principal.Identity.IsAuthenticated; if (!isAuthenticated) return new List<NavigationMenuViewModel>(); var roleIds = await GetUserRoleIds(principal); var data = await (from menu in _context.RoleMenuPermission where roleIds.Contains(menu.RoleId) select menu) .Select(m => new NavigationMenuViewModel() { Id = m.NavigationMenu.Id, Name = m.NavigationMenu.Name, ActionName = m.NavigationMenu.ActionName, ControllerName = m.NavigationMenu.ControllerName, ParentMenuId = m.NavigationMenu.ParentMenuId, }).Distinct().ToListAsync(); return data; } private async Task<List<string>> GetUserRoleIds(ClaimsPrincipal ctx) { var userId = GetUserId(ctx); var data = await (from role in _context.UserRoles where role.UserId == userId select role.RoleId).ToListAsync(); return data; } private string GetUserId(ClaimsPrincipal user) { return ((ClaimsIdentity)user.Identity).FindFirst(ClaimTypes.NameIdentifier)?.Value; } }
我们需要在Startup.cs中注册这个服务,这样依赖注入就可以为它提供服务。可以这样注册: 隐藏,复制Code
services.AddScoped<IDataAccessService, DataAccessService>();
导航菜单 我们将使用视图组件加载导航菜单作为一个部分视图: 隐藏,复制Code
public class NavigationMenuViewComponent : ViewComponent { private readonly IDataAccessService _dataAccessService; public NavigationMenuViewComponent(IDataAccessService dataAccessService) { _dataAccessService = dataAccessService; } public async Task<IViewComponentResult> InvokeAsync() { var items = await _dataAccessService.GetMenuItemsAsync(HttpContext.User); return View(items); } }
在Views中的共享文件夹中创建Components文件夹。在组件中,我们可以创建NavigationMenu文件夹,然后是Default。cshtml视图文件。在这里,层级结构对于它的工作非常重要。 这是部分视图HTML,这里我们将保持我们的范围在2级菜单只有它可以递归到N级但这里为了限制它,我们不使用它。 隐藏,收缩,复制Code
@model List<Mvc.DynamicMenu.Models.NavigationMenuViewModel> @{ ViewData["Title"] = "NavigationMenu"; } <aclass="navbar-brand"asp-area=""asp-controller="Home"asp-action="Index">Dynamic Menu</a> <buttonclass="navbar-toggler"type="button"data-toggle="collapse"data-target="#navbarsExampleDefault"aria-controls="navbarsExampleDefault"aria-expanded="false"aria-label="Toggle navigation"> <spanclass="navbar-toggler-icon"></span> </button> <divclass="collapse navbar-collapse"id="navbarsExampleDefault"> <ulclass="navbar-nav mr-auto"> <liclass="nav-item"> <aclass="nav-link text"asp-area=""asp-controller="Home"asp-action="Index">Home</a> </li> <liclass="nav-item"> <aclass="nav-link text"asp-area=""asp-controller="Home"asp-action="Privacy">Privacy Policy</a> </li> @*Menu Items from the database*@ @foreach (var item in Model) { if (item.ParentMenuId == null) //Level one items will have null parent id { if (!string.IsNullOrWhiteSpace(item.ControllerName)) { <liclass="nav-item active"> <aclass="nav-link text"asp-area=""asp-controller="@item.ControllerName"asp-action="@item.ActionName">@item.Name</a> </li> } var children = Model.Where(x => x.ParentMenuId == item.Id).ToList(); if (children != null) //Level one item has children so append them { <liclass="nav-item dropdown"> <aclass="nav-link dropdown-toggle"href="#"id="dropdown01"data-toggle="dropdown"aria-haspopup="true"aria-expanded="false"> @item.Name</a> <divclass="dropdown-menu"aria-labelledby="dropdown01"> @foreach (var itm in children) { <aclass="dropdown-item"asp-area=""asp-controller="@itm.ControllerName"asp-action="@itm.ActionName">@itm.Name</a> } </div> </li> } } } </ul> <partialname="_LoginPartial"/> </div>
现在我们将创建一个名为Administration的控制器,它有两个动作,角色和用户。 隐藏,复制Code
public class AdministrationController : Controller { private readonly UserManager<IdentityUser> _userManager; private readonly RoleManager<IdentityRole> _roleManager; private readonly ILogger<AdministrationController> _logger; public AdministrationController( UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager, ILogger<AdministrationController> logger) { _userManager = userManager; _roleManager = roleManager; _logger = logger; } public async Task<IActionResult> Roles() { ....... } public async Task<IActionResult> Users() { ........ } }
在控制器之后,我们将为这些操作创建视图,在其中我们可以分别显示角色和用户列表。 让我们再次启动应用程序,它看起来是这样的,对于任何访问者,页面看起来是这样的,但是它会根据分配给用户的角色加载额外的菜单项。 让我们使用用户admin@test.com登录。现在,页面看起来如下所示,其中包含允许根据其角色进行管理的附加菜单项。 下面是如何在登录后用部分视图绘制菜单的。 什么下一个 现在我们有一个问题,如果有人知道页面的URL(比如https://localhost/admination/roles),他们仍然可以访问页面。接下来,我们将了解如何进行基于角色的授权。 结论 当我们通过创建数据库时,我们已经实现了从数据库创建导航菜单的目标h迁移并在开发环境下启动了我们的项目。登录用户可以根据其角色查看菜单项。源代码附呈。我鼓励你跑去看看。如有任何问题或建议,欢迎大家发表意见。感谢你的阅读。 历史 2019年8月26日:初版 本文转载于:http://www.diyabc.com/frontweb/news17318.html