zoukankan      html  css  js  c++  java
  • Asp.Net Core 项目实战之权限管理系统(8) 功能菜单的动态加载

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有

    1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端

    2 Asp.Net Core 项目实战之权限管理系统(2) 功能及实体设计

    3 Asp.Net Core 项目实战之权限管理系统(3) 通过EntityFramework Core使用PostgreSQL

    4 Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现

    5 Asp.Net Core 项目实战之权限管理系统(5) 用户登录

    6 Asp.Net Core 项目实战之权限管理系统(6) 功能管理

    7 Asp.Net Core 项目实战之权限管理系统(7) 组织机构、角色、用户权限

    8 Asp.Net Core 项目实战之权限管理系统(8) 功能菜单的动态加载

    github源码地址

    0 服务层实现

    系统登录后,会在session中记录当前登录用户的信息。

    //检查用户信息
     var user = _userAppService.CheckUser(model.UserName, model.Password);
     if (user != null)
     {
         //记录Session
         HttpContext.Session.SetString("CurrentUserId", user.Id.ToString());
         HttpContext.Session.Set("CurrentUser", ByteConvertHelper.Object2Bytes(user));
         //跳转到系统首页
         return RedirectToAction("Index", "Home");
     }

    由于一个用户可以拥有多个角色,我们需要做的是,根据当前登录用户的Id,得到其拥有的所有角色信息,然后取当前用户所有角色拥有的功能权限求合集,得到的就是当前登录用户所拥有的功能权限信息。

    定义应用服务接口

    在IMenuAppService中定义接口

    /// <summary>
    /// 根据用户获取功能菜单
    /// </summary>
    /// <param name="userId">用户ID</param>
    /// <returns></returns>
    List<MenuDto> GetMenusByUser(Guid userId);

    服务接口实现

    在MenuAppService中实现接口

    /// <summary>
     /// 根据用户获取功能菜单
     /// </summary>
     /// <param name="userId">用户ID</param>
     /// <returns></returns>
     public List<MenuDto> GetMenusByUser(Guid userId)
     {
         List<MenuDto> result = new List<MenuDto>();
         var allMenus = _menuRepository.GetAllList(it=>it.Type == 0).OrderBy(it => it.SerialNumber);
         if (userId == Guid.Empty) //超级管理员
             return Mapper.Map<List<MenuDto>>(allMenus);
         var user = _userRepository.GetWithRoles(userId);
         if (user == null)
             return result;
         var userRoles = user.UserRoles;
         List<Guid> menuIds = new List<Guid>();
         foreach (var role in userRoles)
         {
             menuIds = menuIds.Union(_roleRepository.GetAllMenuListByRole(role.RoleId)).ToList();
         }
         allMenus = allMenus.Where(it => menuIds.Contains(it.Id)).OrderBy(it => it.SerialNumber);
         return Mapper.Map<List<MenuDto>>(allMenus);
     }

    1 使用ViewComponent动态加载菜单

    在以前的Asp.net MVC中,我们会经常使用@Html.Action来发起一个ChildAction请求,渲染并得到一个分部视图填充的主功能视图中,以此来实现一些公用的或者是独立的界面区域。在Asp.Net Core中,不在存在将以ViewComponent替代。

    你可以将View Component看做是一个mini的Controller——它只负责渲染一小部分内容,而非全部响应,所有分部视图能解决的问题,你都可以使用View Component来解决,比如:动态导航菜单、Tag标签、登录窗口、购物车、最近阅读文章等等。

    1.0 ViewComponent创建

    ViewComponent创建非常简单,只需要将我们的类继承自ViewComponent类就行了。每个ViewComponent需要包括一个名称为Invoke的约定方法,你可以在此方法中传入你需要的任何参数,系统也支持InvokeAsync方法实现异步功能,此约定方法为该ViewComponent的最终输出出口。

    在Fonour.MVC项目中新建一个名称为“Components”的文件夹,用来存放我们所有的视图组件类,在该文件夹下新建一个名称为NavigationViewComponent的组件。

    [ViewComponent(Name = "Navigation")]
    public class NavigationViewComponent : ViewComponent
    {
        private readonly IMenuAppService _menuAppService;
        private readonly IUserAppService _userAppService;
        public NavigationViewComponent(IMenuAppService menuAppService, IUserAppService userAppService)
        {
            _menuAppService = menuAppService;
            _userAppService = userAppService;
        }
    
        public IViewComponentResult Invoke()
        {
            var userId = HttpContext.Session.GetString("CurrentUserId");
            var menus = _menuAppService.GetMenusByUser(Guid.Parse(userId));
            return View(menus);
        }
    }

    1.1 组件对应的视图文件创建

    上面实现的约定方法Invoke中,最后Return View(Menus),与我们控制器中Action返回视图的方法很相似。ViewComponent寻找视图也是遵照约定的。他会自动从以下路径去寻找对应的视图文件。

    /Views/[CurrentController]/Components/[NameOfComponent]/Default.cshtml
    /Views/Shared/Components/[NameOfComponent]/Default.cshtml

    对于某个具体功能对应的组件,我们最好按照上面第一种路径规则,把对应的视图文件放在功能对应的Controller下面

    对于系统级别的视图组件,比如我们现在要实现的左侧功能导航菜单,建议按照第二种路径规则,放在Views/Shared下面。

    在Views/Shared文件夹下新建一个名称为Components的文件夹,然后在该文件夹下创建一个名称为“Navigation”的文件夹,注意这个文件夹名与我们上面定义的组件名称是要保持一直的。

    image

    在“Navigation”文件夹下创建一个名称为Default.cshtml的视图页。内容如下:

    @model List<Fonour.Application.MenuApp.Dtos.MenuDto>
    <li class="header">权限管理</li>
    @foreach (var menu in Model)
    {
        var isActive = ViewBag.CurrentMenu == menu.Code; //判断当前功能是否处于激活
        <li class="@(isActive ? "active" : "")"><a href="@menu.Url"><i class="fa fa-link"></i> <span>@menu.Name</span></a></li>
    }

    该视图接受一个在我们组件类中返回的菜单集合对象,并根据此菜单集合渲染我们需要的菜单。我们根据ViewBag.CurrentMenu是否等于当前菜单的编码,来确定该菜单是否属于激活状态,这样可以实现我们单击某个菜单界面刷新后,该功能菜单处于激活状态。

    基于此,我们需要在每个功能页面指定ViewBag.CurrentMenu的值。如用户管理功能,我们在/Views/User/Index.cshtml顶部添加如下代码即可。

    @{
        ViewBag.CurrentMenu = "User";
    }

    当然,如果你觉得这种硬编码的方式你不喜欢,你可以在菜单单击的时候,把当前菜单对应的code值传至服务端,由服务端在返回对应的视图之前,指定ViewBag.CurrentMenu的值即可。

    1.2 ViewComponent的使用

    我们修改布局页_Layout.cshtml中菜单加载部分的代码,将写死的功能菜单信息,修改为通过使用ViewComponent根据用户Id动态加载功能菜单。

    <ul class="sidebar-menu">
        @await Component.InvokeAsync("Navigation");
        @*<li class="treeview">
                <a href="#">
                    <i class="fa fa-link"></i> <span>Multilevel</span>
                    <span class="pull-right-container">
                        <i class="fa fa-angle-left pull-right"></i>
                    </span>
                </a>
                <ul class="treeview-menu">
                    <li><a href="#">Link in level 2</a></li>
                    <li><a href="#">Link in level 2</a></li>
                </ul>
            </li>*@
    </ul>

    此时我们使用一个只分配了其中三个菜单权限的账户登录系统,一切按照我们预想的,可以根据当前登录用户动态加载该用户所拥有权限的功能菜单了,而且我们进入某个功能后,页面刷新后已经可以自动把我们进入的功能菜单设置为激活状态了。

    image

    image

    2 总结

    本次主要学习了ViewComponent的使用,实现了功能菜单的动态加载。

    最开始是本着自己学习的态度写这个系列的,内容总体来说属于基础入门级的,到现在也算是基本功能都已经实现了,里面有很多不足或不完善的地方,可以根据需要进行调整吧。

    从孩子出生,转眼间到现在3个月了,每天熬夜熬的精疲力尽,工作上也是忙的不可开交,有些朋友给我的留言中的,我有空看到就回复了,有些朋友的问题,有可能我没时间看到,也有可能实在抽不出时间去帮你找问题所在,还得麻烦您自己多动手实践一下了,请见谅。

  • 相关阅读:
    感到工作效率低?是因为你缺少这套“外贸企业邮箱”办公软件
    用企业邮箱的好处,企业邮箱手机微信使用
    公司企业邮箱注册,公司为什么用企业邮箱?
    中小企业公司邮箱一般用哪个?申请企业邮箱的流程
    公司企业邮箱注册,企业邮箱怎么办理
    array_replace — 使用传递的数组替换第一个数组的元素
    array_replace_recursive — 使用传递的数组递归替换第一个数组的元素
    array_reduce — 用回调函数迭代地将数组简化为单一的值
    array_rand — 从数组中随机取出一个或多个单元
    array_push — 将一个或多个单元压入数组的末尾(入栈)
  • 原文地址:https://www.cnblogs.com/fonour/p/6425635.html
Copyright © 2011-2022 走看看