9

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

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

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

例如,对于 BiologistController.cs

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

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

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

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

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

好吧,你明白了……现在是真正的问题……


从这个 StackOverflow 主题关于在 ASP.NET 中构建动态菜单的答案开始,我试图了解如何完全实现这一点。(我是新手,需要更多指导,所以请和我一起裸露。)

答案建议扩展 Controller 类(称为“ExtController”),然后让每个新的 WhatController 从 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
        }
    }
}

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

还!随意超出此问题的范围,并提出解决此“Dynamic Site.Master Menu based on Roles”问题的替代方法。我是第一个承认这可能不是最好的方法的人。

编辑

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

4

3 回答 3

3

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

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 将没有动态菜单。(这可能很有用,在某些情况下,我认为......)

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

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

就是这样!:-)

于 2010-06-11T13:56:30.390 回答
3

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

于 2010-06-08T18:05:24.647 回答
0

我遇到了同样的问题,需要逻辑留在控制器端。但我确实喜欢 John 的方法,因为它使用系统过滤器来决定某个操作是否被授权。如果它对任何人有帮助,以下代码HtmlHelper从 John 的方法中删除:

    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;
    }
于 2019-05-29T18:40:00.540 回答