如何使用C#中的Reflection使用属性查找控制器? (或如何构建动态Site.Master菜单?)

也许在进入标题问题之前我应该​​备份并扩大范围……

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

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

例如,对于BiologistController.cs

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

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

[主页] [天文学家] [物理学家] [关于]

当然,它不会列出“生物学家”或“化学家”控制器索引页面的链接。

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

好吧,你们大家都有了想法…现在回答真正的问题……


StackOverflow主题关于ASP.NET中动态菜单构建的答案开始,我试图理解如何完全实现它。

答案建议扩展Controller类(称之为“ExtController”),然后让每个新的WhateverControllerinheritance自ExtController。

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

我想象它是这样的:

 public class ExtController : Controller { protected static Dictionary<Type,List> ControllerRolesDictionary; protected override void OnActionExecuted(ActionExecutedContext filterContext) { // build list of menu items based on user's permissions, and add it to ViewData IEnumerable menu = BuildMenu(); ViewData["Menu"] = menu; } private IEnumerable 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 } } } 

这可行吗? 如果是这样,我如何在ExtController构造函数中执行Reflection以发现[Authorize]属性和相关角色(如果有的话)

也! 在这个问题上随意超出范围并建议另一种方法来解决这个“基于角色的动态Site.Master菜单”问题。 我是第一个承认这可能不是最佳方法的人。

编辑

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

我更喜欢链接到我的菜单中的所有内容并创建一个HtmlHelper,它根据[Authorize]属性检查链接是否可访问 。

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

 public abstract class ExtController : Controller { protected static Dictionary> RolesControllerDictionary; protected override void OnActionExecuted(ActionExecutedContext filterContext) { // build list of menu items based on user's permissions, and add it to ViewData IEnumerable menu = BuildMenu(); ViewData["Menu"] = menu; } private IEnumerable BuildMenu() { // Code to build a menu var dynamicMenu = new List(); 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>(); // 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(); } // Add controller to List of controllers associated with role (removing "controller" from name) RolesControllerDictionary[role].Add(controller.Name.Replace(CONTROLLER,"")); } } } } } 

要使用,只需:

  • 将[Authorize(Roles =“SomeRole1,SomeRole2,SomeRole3等”)添加到Controller类
  • 用“ExtController”替换inheritance的“Controller”。

例如:

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

如果不将“Controller”替换为“ExtController”,则该Controller将没有动态菜单。 (在某些情况下,这可能很有用,我认为…)

在我的Site.Master文件中,我将“菜单”部分更改为如下所示:

  

就是这样! 🙂