基于用户权限的动态菜单生成

Dynamic Menu generation based on user permissions

我正在尝试开发一个通用功能,可以让我获得应用程序的所有操作和所有控制器。我想通过所有仅 GET 而不是 POST 的操作来过滤这些控制器操作。换句话说,我想在应用程序中拥有所有可能的路由。我正在使用反射来做这件事,但还没有真正能够通过。我想使用反射,所以每次添加任何新的控制器和动作时,它们也会在此列表中。

所以,我想要这个的原因是,我想根据用户的角色、声明、and/or 任何其他用户权限,将这些操作基本上显示为用户的动态菜单。这应该只向他们展示允许的内容。

任何这方面的线索都将不胜感激。

谢谢。

[Table("Menus")]
public class Menu
{
    public int ID { get; set; }

    public string Name { get; set; }

    public string CssClass { get; set; }

    public int SortOrder { get; set; }

    public MenuLevel MenuLevel { get; set; }

    public List<MenuItem>  MenuItems { get; set; }

}

[Table("MenuItems")]
public class MenuItem
{
    public int ID { get; set; }

    public string Name { get; set; }

    public string CssClass { get; set; }

    public int SortOrder { get; set; }

}

 private async Task<List<Menu>> GetAndSaveAllControllerActions()
    {
        List<Menu> menus = new();
        // loop through all potential controller types
        foreach (var controllerType in Assembly.GetExecutingAssembly().GetExportedTypes().Where(t => typeof(ControllerBase).IsAssignableFrom(t)))
        {
            Menu menu = new()
            {
                Title = controllerType.Name,
                MenuLevel = MenuLevel.Level1,
                SortOrder = 1,
                RouteAddress = @$"\{controllerType.Name}\"
            };

            menu.Title = menu.Title.Replace("Controller", "");
            // get their all public instance methods (excluding inherited ones)
            // these are the so-called action methods
            foreach (var actionMethod in controllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
            {
                // get attributes assigned with the method
                var attributes = actionMethod.GetCustomAttributes(false);

                // filter methods marked with [NonAction]
                // filter out the ones with ref or out params too here if 
                // you have such things
                if (!attributes.Any(i => i is NonActionAttribute))
                {
                    // filter POSTs out as you wish
                    if (!attributes.Any(i => i is HttpPostAttribute))
                    {
                        // you can compose something from various info available at this point e.g.
                        // through 'controllerType', 'actionMethod' or other attributes if they
                        // exists in 'attributes' (e.g. RouteAttribute)
                        menu.MenuItems.Add(new MenuItem()
                        {
                            MenuID = menu.MenuID,
                            Title = actionMethod.Name,
                            RouteAddress = menu.RouteAddress + $"{actionMethod.Name}"
                        });
                    }
                }
            }
            menus.Add(menu);
        }
        await CheckForDatabaseExistence(menus);

        return menus;
    }

    private Task<int> CheckForDatabaseExistence(List<Menu> menus)
    {
        var dbMenus = _context.Menus.ToList();
        foreach (var menu in menus)
            if (!dbMenus.Contains(menu))
            {
                _context.Menus.Add(menu);
                return _context.SaveChangesAsync();
            }

        return Task.FromResult(0);
    }

我已经修改了代码,以便将来对任何人有所帮助。 感谢 cly,他在下面的回答对我们有所帮助。

我相信还有其他方法可以通过 MVC 的路由来完成您提到的这项任务。但是您以 reflection-based 方式开始编写代码,因此最好继续使用这种方式而不是混合使用各种方式。

在 MVC 中,动作方法可以是基于 class 的 Controller 的任何方法,其中:

  • public
  • 不是static
  • 没有棘手的参数,例如标有 refout
  • 的参数
  • 未标记 [NonAction] 属性

下面的 PoC 代码向您展示了如何使用这些约束来收集您需要的数据。

(代码被实现为 TestMethod 因为测试执行提供了一个非常方便的快速微执行环境,可以在您的开发域的所有内容都可用时临时尝试一些事情。这个假的“测试”可能是如果你想要的东西被尝试过并且你继续前进,就简单地放弃。)

[TestMethod]
public void MyTestMethod()
{
    // loop through all potential controller types
    foreach (var controllerType in Assembly
        .GetExecutingAssembly()
        .GetExportedTypes()
        .Where(t => typeof(ControllerBase).IsAssignableFrom(t)))
    {
        // get their all public instance methods (excluding inherited ones)
        // these are the so-called action methods
        foreach(var actionMethod in controllerType.GetMethods(
            System.Reflection.BindingFlags.Instance | 
            System.Reflection.BindingFlags.Public | 
            System.Reflection.BindingFlags.DeclaredOnly))
        {
            // get attributes assigned with the method
            var attributes = actionMethod.GetCustomAttributes(false);

            // filter methods marked with [NonAction]
            // filter out the ones with ref or out params too here if 
            // you have such things
            if (!attributes.Any(i=>i is System.Web.Http.NonActionAttribute))
            {
                // filter POSTs out as you wish
                if (!attributes.Any(i => i is System.Web.Http.HttpPostAttribute))
                {
                    // you can compose something from various info available at this point e.g.
                    // through 'controllerType', 'actionMethod' or other attributes if they
                    // exists in 'attributes' (e.g. RouteAttribute)
                    var controllerName = controllerType.Name;
                    var actionName = actionMethod.Name;
                }
            }
        }
    }
}