代码之家  ›  专栏  ›  技术社区  ›  Pretzel

ASP.NET MVC:如何使用C#中的反射查找具有[Authorize]属性的控制器(或者如何建立动态网站。主菜单?)

  •  9
  • Pretzel  · 技术社区  · 16 年前

    也许我应该在进入标题问题之前,先退后一步,扩大范围。。。

    我目前正在用ASP.NET MVC 1.0编写一个web应用程序(虽然我的电脑上安装了MVC 2.0,所以我并不完全局限于1.0)--我已经开始了标准的MVC项目,它有您的基本“欢迎使用ASP.NET MVC”,并在右上角显示了[Home]选项卡和[About]选项卡。很标准,对吧?

    我添加了4个新的控制器类,我们称它们为“天文学家”、“生物学家”、“化学家”和“物理学家”。附加到每个新控制器类的是[Authorize]属性。

    例如,对于controller.cs

    [Authorize(Roles = "Biologist,Admin")]
    public class BiologistController : Controller
    { 
        public ActionResult Index() { return View(); }
    }
    

    这些[Authorize]标签自然会根据角色限制哪些用户可以访问不同的控制器,但我希望在网站顶部动态构建一个菜单。基于用户所属角色的母版页。例如,如果“JoeUser”是“天文学家”和“物理学家”角色的成员,导航菜单会显示:

    [关于]

    当然,它会

    或者,如果“JohnAdmin”是“Admin”角色的成员,那么导航栏中将显示指向所有4个控制器的链接。

    好吧,你一定会明白的。。。现在问真正的问题。。。


    从开始 the answer from this StackOverflow topic about Dynamic Menu building in ASP.NET ,我正在努力理解我将如何充分实现这一点(我是个新手,需要更多的指导,所以请跟我说。)

    答案是扩展控制器类(称之为“ExtController”),然后让每个新的WhateverController从ExtController继承。

    我的结论是,我需要在这个ExtController构造函数中使用反射来确定哪些类和方法附加了[Authorize]属性来确定角色。然后使用静态字典,将角色和控制器/方法存储在键值对中。

    public class ExtController : Controller
    {
        protected static Dictionary<Type,List<string>> ControllerRolesDictionary;
    
        protected override void OnActionExecuted(ActionExecutedContext filterContext)   
        {   
            // build list of menu items based on user's permissions, and add it to ViewData  
            IEnumerable<MenuItem> menu = BuildMenu();  
            ViewData["Menu"] = menu;
        }
    
        private IEnumerable<MenuItem> BuildMenu()
        {
            // Code to build a menu
            SomeRoleProvider rp = new SomeRoleProvider();
            foreach (var role in rp.GetRolesForUser(HttpContext.User.Identity.Name))
            {
    
            }
        }
    
        public ExtController()
        {
            // Use this.GetType() to determine if this Controller is already in the Dictionary
            if (!ControllerRolesDictionary.ContainsKey(this.GetType()))
            {
                // If not, use Reflection to add List of Roles to Dictionary 
                // associating with Controller
            }
        }
    }
    

    也!在这个问题上,请随意超出范围,并建议解决这个“基于角色的动态站点主菜单”问题的另一种方法。我是第一个承认这可能不是最好的方法。

    经过大量的阅读和实验,我想出了自己的解决办法。下面是我的答案。欢迎任何建设性的反馈/批评!

    2 回复  |  直到 5 年前
        1
  •  3
  •   Community Mohan Dere    9 年前

    我更喜欢链接到菜单和 creating a HtmlHelper which checks to see if a link is accessible or not 基于[Authorize]属性。

        2
  •  3
  •   Pretzel    16 年前

    好的,所以我决定像我最初提议的那样充实我自己的扩展控制器类。这是一个非常基本的版本。我可以看到各种各样的改进方法(进一步扩展、收紧代码等等),但我想我会提供我的基本结果,因为我想还有很多人想要类似的东西,但可能不想要所有额外的东西。

    public abstract class ExtController : Controller
    {
        protected static Dictionary<string, List<string>> RolesControllerDictionary;
        protected override void OnActionExecuted(ActionExecutedContext filterContext)   
        {   
            // build list of menu items based on user's permissions, and add it to ViewData  
            IEnumerable<MenuItem> menu = BuildMenu();  
            ViewData["Menu"] = menu;
        }
    
        private IEnumerable<MenuItem> BuildMenu()
        {
            // Code to build a menu
            var dynamicMenu = new List<MenuItem>();
            SomeRoleProvider rp = new SomeRoleProvider();
            // ^^^^^INSERT DESIRED ROLE PROVIDER HERE^^^^^
            rp.Initialize("", new NameValueCollection());
            try
            {   // Get all roles for user from RoleProvider
                foreach (var role in rp.GetRolesForUser(HttpContext.User.Identity.Name))
                {   // Check if role is in dictionary
                    if (RolesControllerDictionary.Keys.Contains(role))
                    {   
                        var controllerList = RolesControllerDictionary[role];
                        foreach (var controller in controllerList)
                        {   // Add controller to menu only if it is not already added
                            if (dynamicMenu.Any(x => x.Text == controller))
                            { continue; }
                            else
                            { dynamicMenu.Add(new MenuItem(controller)); }
                        }
                    }
                }
            }
            catch { }   // Most role providers can throw exceptions. Insert Log4NET or equiv here.   
            return dynamicMenu; 
        }
    
        public ExtController()
        {
            // Check if ControllerRolesDictionary is non-existant
            if (RolesControllerDictionary == null)
            {
                RolesControllerDictionary = new Dictionary<string, List<string>>();
                // If so, use Reflection to add List of all Roles associated with Controllers
                const bool allInherited = true;
                const string CONTROLLER = "Controller";
                var myAssembly = System.Reflection.Assembly.GetExecutingAssembly();
    
                // get List of all Controllers with [Authorize] attribute
                var controllerList = from type in myAssembly.GetTypes()
                                     where type.Name.Contains(CONTROLLER)
                                     where !type.IsAbstract
                                     let attribs = type.GetCustomAttributes(allInherited)
                                     where attribs.Any(x => x.GetType().Equals(typeof(AuthorizeAttribute)))
                                     select type;
                // Loop over all controllers
                foreach (var controller in controllerList)
                {   // Find first instance of [Authorize] attribute
                    var attrib = controller.GetCustomAttributes(allInherited).First(x => x.GetType().Equals(typeof(AuthorizeAttribute))) as AuthorizeAttribute;
                    foreach (var role in attrib.Roles.Split(',').AsEnumerable())
                    {   // If there are Roles associated with [Authorize] iterate over them
                        if (!RolesControllerDictionary.ContainsKey(role))
                        { RolesControllerDictionary[role] = new List<string>(); }
                        // Add controller to List of controllers associated with role (removing "controller" from name)
                        RolesControllerDictionary[role].Add(controller.Name.Replace(CONTROLLER,""));
                    }
                }
            }
        }
    }
    

    要使用,只需:

    • 将继承的“Controller”替换为“ExtController”。

    例如:

    [Authorize(Roles = "Biologist,Admin")]
    public class BiologistController : ExtController
    {
        public ActionResult Index()
        { return View(); }
    }
    

    如果不将“Controller”替换为“ExtController”,那么该控制器就没有动态菜单(我想,在某些情况下,这可能很有用……)

    站点.主 档案,我改了 “菜单”部分 看起来像这样:

    <ul id="menu">              
        <li><%= Html.ActionLink("Home", "Index", "Home")%></li>
        <%  if (ViewData.Keys.Contains("Menu"))
            {
              foreach (MenuItem menu in (IEnumerable<MenuItem>)ViewData["Menu"])
              { %>
        <li><%= Html.ActionLink(menu.Text, "Index", menu.Text)%></li>           
         <%   } 
            }   
         %>       
        <li><%= Html.ActionLink("About", "About", "Home")%></li>
    </ul>
    

    就这样!:-)

        3
  •  0
  •   Bob    7 年前

    我遇到了同样的问题,需要逻辑留在控制器端。但是我很喜欢John的方法,因为它使用系统过滤器来决定一个操作是否被授权。以下代码删除了 HtmlHelper 从约翰的方法来看:

        protected bool HasActionPermission(string actionName, string controllerName)
        {
            if (string.IsNullOrWhiteSpace(controllerName))
                return false;
    
            var controller = GetControllerByName(ControllerContext.RequestContext, controllerName);
            var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
            var actionDescriptor = controllerDescriptor.FindAction(ControllerContext, actionName);
            return ActionIsAuthorized(ControllerContext, actionDescriptor);
        }
    
        private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
        {
            if (actionDescriptor == null)
                return false; // action does not exist so say yes - should we authorise this?!
    
            AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor);
    
            // run each auth filter until on fails
            // performance could be improved by some caching
            foreach (var filter in FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor))
            {
                var authFilter = filter.Instance as IAuthorizationFilter;
    
                if (authFilter == null)
                    continue;
    
                authFilter.OnAuthorization(authContext);
    
                if (authContext.Result != null)
                    return false;
            }
    
            return true;
        }
    
        private static ControllerBase GetControllerByName(RequestContext context, string controllerName)
        {
            IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
    
            IController controller = factory.CreateController(context, controllerName);
    
            if (controller == null)
            {
                throw new InvalidOperationException(
    
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        "Controller factory {0} controller {1} returned null",
                        factory.GetType(),
                        controllerName));
            }
            return (ControllerBase)controller;
        }