如何在C#中使用Reflection查找具有[Authorize]属性的控制器(或如何构建Dynamic Site.Master菜单) [英] How to find Controllers with [Authorize] attributes using Reflection in C# (or How to build Dynamic Site.Master Menus)

查看:78
本文介绍了如何在C#中使用Reflection查找具有[Authorize]属性的控制器(或如何构建Dynamic Site.Master菜单)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在深入探讨标题问题之前,也许我应该备份并扩大范围...

Maybe I should back-up and widen the scope before diving into the title question...

我目前正在用ASP.NET MVC 1.0编写一个Web应用程序(尽管我确实在PC上安装了MVC 2.0,所以我并不完全局限于1.0)-我已经从标准MVC项目开始它具有基本的欢迎使用ASP.NET MVC",并在右上角显示[主页]选项卡和[关于]选项卡.很标准,对吧?

I'm currently writing a web app in ASP.NET MVC 1.0 (although I do have MVC 2.0 installed on my PC, so I'm not exactly restricted to 1.0) -- I've started with the standard MVC project which has your basic "Welcome to ASP.NET MVC" and shows both the [Home] tab and [About] tab in the upper-right corner. Pretty standard, right?

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

I've added 4 new Controller classes, let's call them "Astronomer", "Biologist", "Chemist", and "Physicist". Attached to each new controller class is the [Authorize] attribute.

例如,对于BiologistController.cs

For example, for the BiologistController.cs

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

这些[Authorize]标签自然会限制哪些用户可以访问不同的控制器(取决于角色),但是我想根据用户所属的角色在Site.Master页面的网站顶部动态构建菜单. .因此,例如,如果"JoeUser"是天文学家"和物理学家"角色的成员,则导航菜单将显示:

These [Authorize] tags naturally limit which user can access different controllers depending on Roles, but I want to dynamically build a Menu at the top of my website in the Site.Master Page based on the Roles the user is a part of. So for example, if "JoeUser" was a member of Roles "Astronomer" and "Physicist", the navigation menu would say:

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

[Home] [Astronomer] [Physicist] [About]

自然,它不会列出指向生物学家"或化学家"控制器索引页面的链接.

And naturally, it would not list links to "Biologist" or "Chemist" controller Index page.

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

Or if "JohnAdmin" was a member of Role "Admin", links to all 4 controllers would show up in the navigation bar.

好吧,您很容易理解...现在是一个真正的问题...

Ok, you prolly get the idea... Now for the real question...

有关在ASP.NET中构建动态菜单的这个StackOverflow主题的答案 ,我试图了解如何完全实现这一点.

Starting with the answer from this StackOverflow topic about Dynamic Menu building in ASP.NET, I'm trying to understand how I would fully implement this.

答案提出了扩展Controller类(称为"ExtController"),然后让每个新的WhateverController从ExtController继承.

The answer proposes Extending the Controller class (call it "ExtController") and then have each new WhateverController inherit from ExtController.

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

My conclusion is that I would need to use Reflection in this ExtController Constructor to determine which Classes and Methods have [Authorize] attributes attached to them to determine the Roles. Then using a Static Dictionary, store the Roles and Controllers/Methods in key-value pairs.

我想像这样:

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
        }
    }
}

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

Is this doable? If so, how do I perform Reflection in the ExtController constructor to discover the [Authorize] attribute and related Roles (if any)

还!随意讨论此问题,并提出解决此基于角色的动态Site.Master菜单"问题的替代方法.我是第一个承认这可能不是最佳方法的人.

ALSO! Feel free to go out-of-scope on this question and suggest an alternate way of solving this "Dynamic Site.Master Menu based on Roles" problem. I'm the first to admit that this may not be the best approach.

推荐答案

好,所以我决定像我最初建议的那样充实自己的扩展控制器类.这是一个非常基本的版本.我可以看到使它更好的各种方法(进一步扩展,加强代码等),但我想我会提供基本的结果,因为我想还有很多其他人想要类似的东西,但可能不希望全部临时演员.

Ok, so I decided to flesh out my own Extended Controller class like I originally proposed. Here is a very basic version. I can see various ways of making this better (extending further, tightening up the code, etc.) but I thought I would offer up my basic results because I imagine there are plenty of other people that want something similar, but might not want all the extras.

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,""));
                }
            }
        }
    }
}

要使用,只需:

  • 将[Authorize(Roles ="SomeRole1,SomeRole2,SomeRole3,etc."]添加到控制器类中
  • 将继承的"Controller"替换为"ExtController".

例如:

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

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

If you don't replace "Controller" with "ExtController", then that Controller won't have a dynamic menu. (This could be useful, in some scenarios, I think...)

在我的 Site.Master 文件中,我更改了菜单"部分,使其看起来像这样:

In my Site.Master file, I changed the "menu" section to look like this:

<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>

就是这样! :-)

这篇关于如何在C#中使用Reflection查找具有[Authorize]属性的控制器(或如何构建Dynamic Site.Master菜单)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆