目录
目标
介绍
使用的组件
创建Web项目
迁移
数据服务
导航菜单
接下来是什么
结论
Download source code - 977.3 KB
目标几年前,我不得不从数据库加载导航菜单并使用Web表单创建菜单控件,因此从数据库加载菜单数据的主要思想是根据用户角色进行过滤。最后,我们将按角色过滤数据。在这里,我们必须使用ASP.NET Core 2.2 MVC应用程序来做到这一点。
介绍我在MVC 6 .NET Core中遇到了此要求,以便从数据库动态生成基于角色的导航菜单,因此可以使用它浏览网站和管理面板以进行管理,以分配角色,权限和其他应用程序维护。在此系统中,角色数量受到限制,因此基于角色的授权非常合适。
使用的组件这是构建和测试所提供的演示代码所需的组件。
- 如果您没有Professional或Enterprise,则下载最新的Visual Studio 2019社区版本
- 我正在使用SQL Server Developer版本17.9.1,可以从此链接下载。
在Visual Studio 2019中创建Web应用程序。
选择语言作为C#,将项目类型选择为Web,然后选择第一个模板ASP.NET Core Web Application,然后单击下一步。
提供项目名称并选择物理路径,然后单击“创建”。
选择Web应用程序(模型-视图-控制器),然后单击“身份验证”下右侧的“更改”按钮。之后,选择个人用户帐户,单击确定以关闭弹出窗口,然后单击创建。
现在,该项目已经设置并可以运行,但是我们没有基于模型创建任何数据库,因此首先,我们需要在appsettings.json文件中更改连接字符串。我将使用本地主机作为Windows身份验证的服务器,以下是我的连接字符串。
"DefaultConnection": "Server=localhost;Database=DynamicMenu;
Trusted_Connection=True;MultipleActiveResultSets=true"
但是,如果我们在此级别上创建数据库,则只有Identity如下所示的表:
但是在我们的例子中,我们需要另外两个表,这些表将通过使用代码来创建,首先定义它们的实体,然后将它们添加到我们的context类中。
[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 options)
: base(options)
{
}
public DbSet RoleMenuPermission { get; set; }
public DbSet NavigationMenu { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
迁移
现在我们需要运行迁移,然后更新数据库,Enable-Migrations命令已过时,因此我们需要从Migrations文件夹中删除所有内容,然后运行add migration命令。
add-migration InitialVersion
它将在Migrations文件夹中创建一些文件,然后如果您的连接字符串正确,我们需要运行update-database命令,然后将如下所示创建数据库:
如果想了解更详细的信息,可阅读另一篇文章:
- 播种数据MVC 6 .NET Core应用程序
对于当前方案,我们的seed将具有所有导航菜单项、用户、角色和权限。因此,它将更加复杂。
现在,我们已经拥有所有实体的数据库,因此让我们在开发环境中运行应用程序,它将在数据库中插入seed数据。
数据服务我们将创建一个数据服务来与数据库通信,它非常简单,它具有一个主要功能GetMenuItemsAsync ,该功能在按角色过滤后返回Navigation菜单视图模型。
public class DataAccessService : IDataAccessService
{
private readonly ApplicationDbContext _context;
public DataAccessService(ApplicationDbContext context)
{
_context = context;
}
public async Task
GetMenuItemsAsync(ClaimsPrincipal principal)
{
var isAuthenticated = principal.Identity.IsAuthenticated;
if (!isAuthenticated)
return new List();
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 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中注册此服务,以便依赖注入可以为其提供服务。可以这样注册:
services.AddScoped();
导航菜单
我们将使用“视图组件”将导航菜单加载为部分视图:
public class NavigationMenuViewComponent : ViewComponent
{
private readonly IDataAccessService _dataAccessService;
public NavigationMenuViewComponent(IDataAccessService dataAccessService)
{
_dataAccessService = dataAccessService;
}
public async Task InvokeAsync()
{
var items = await _dataAccessService.GetMenuItemsAsync(HttpContext.User);
return View(items);
}
}
在Views的Shared文件夹中创建一个Components文件夹。因此,在Components中,我们可以创建NavigationMenu文件夹,然后创建Default.cshtml视图文件。在这里,该层次结构对于它的工作非常重要。
这是HTML的局部视图,在这里我们将范围保持在2级菜单,只有它可以递归地转到N级,但是为了保持它的有限性,我们将不使用它。
@model List
@{
ViewData["Title"] = "NavigationMenu";
}
Dynamic Menu
-
Home
-
Privacy Policy
@*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))
{
-
@item.Name
}
var children = Model.Where(x => x.ParentMenuId == item.Id).ToList();
if (children != null) //Level one item has children so append them
{
-
@item.Name
@foreach (var itm in children)
{
@itm.Name
}
}
}
}
现在,我们将创建一个名为Administration的控制器,其中包含两个操作Roles 和Users。
public class AdministrationController : Controller
{
private readonly UserManager _userManager;
private readonly RoleManager _roleManager;
private readonly ILogger _logger;
public AdministrationController(
UserManager userManager,
RoleManager roleManager,
ILogger logger)
{
_userManager = userManager;
_roleManager = roleManager;
_logger = logger;
}
public async Task Roles()
{
.......
}
public async Task Users()
{
........
}
}
控制器后,我们将创建视图的这些动作,我们可以分别显示Roles和Users列表。
让我们再次启动该应用程序,以下是它的外观,对于任何访问者来说页面都是这样,但是它将根据分配给用户的角色加载其他菜单项。
让我们以用户admin@test.com登录。现在,该页面如下所示,并根据其角色允许其他菜单项进行管理。
因此,这是登录后如何用部分视图绘制该菜单的方法。
接下来是什么现在我们遇到一个问题,如果有人知道页面的URL,例如https://localhost/administration/roles,他们仍然可以访问该页面。接下来,我们将看看如何进行基于角色的授权。
结论通过迁移创建数据库并在开发环境下启动项目时,我们已经实现了从数据库创建导航菜单的目标。登录的用户根据其角色查看菜单项。源代码已随附。我鼓励您运行并查看它。