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

原文地址:https://www.cnblogs.com/Dincat/p/13494066.html