ASP.NET MVC:如何使用C#中的反射查找具有[Authorize]属性的控制器?(或者如何构建动态站点。主菜单?)
也许我应该在进入标题问题之前,先做些准备,扩大范围 我目前正在用ASP.NET MVC 1.0编写一个web应用程序(尽管我的电脑上安装了MVC 2.0,所以我并不完全局限于1.0)——我从标准MVC项目开始,该项目包含基本的“欢迎使用ASP.NET MVC”,并在右上角显示[Home]选项卡和[About]选项卡。相当标准,对吧 我添加了4个新的控制器类,让我们称它们为“天文学家”、“生物学家”、“化学家”和“物理学家”。[Authorize]属性附加到每个新控制器类 例如,对于biological controller.csASP.NET MVC:如何使用C#中的反射查找具有[Authorize]属性的控制器?(或者如何构建动态站点。主菜单?),c#,asp.net,asp.net-mvc,reflection,authorize-attribute,C#,Asp.net,Asp.net Mvc,Reflection,Authorize Attribute,也许我应该在进入标题问题之前,先做些准备,扩大范围 我目前正在用ASP.NET MVC 1.0编写一个web应用程序(尽管我的电脑上安装了MVC 2.0,所以我并不完全局限于1.0)——我从标准MVC项目开始,该项目包含基本的“欢迎使用ASP.NET MVC”,并在右上角显示[Home]选项卡和[About]选项卡。相当标准,对吧 我添加了4个新的控制器类,让我们称它们为“天文学家”、“生物学家”、“化学家”和“物理学家”。[Authorize]属性附加到每个新控制器类 例如,对于biologi
[Authorize(Roles = "Biologist,Admin")]
public class BiologistController : Controller
{
public ActionResult Index() { return View(); }
}
这些[Authorize]标签自然会根据角色限制哪些用户可以访问不同的控制器,但我想在网站顶部动态构建一个菜单。基于用户所属角色的母版页。例如,如果“JoeUser”是“天文学家”和“物理学家”角色的成员,导航菜单会显示:
[主页][天文学家][物理学家]
[关于]
当然,它不会列出指向“生物学家”或“化学家”控制器索引页的链接
或者,如果“JohnAdmin”是角色“Admin”的成员,则导航栏中将显示指向所有4个控制器的链接
好吧,你一定会明白的。。。现在是真正的问题
首先,我试图理解我将如何充分实现这一点。(我是新手,需要多一点指导,请跟我说。) 答案建议扩展控制器类(称之为“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
}
}
}
<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>
公共类ExtController:Controller
{
受保护的静态字典控制器;
受保护的覆盖无效OnActionExecuted(ActionExecutedContext筛选器上下文)
{
//基于用户权限生成菜单项列表,并将其添加到ViewData
IEnumerable menu=BuildMenu();
ViewData[“菜单”]=菜单;
}
私有IEnumerable构建菜单()
{
//生成菜单的代码
SomeRoleProvider rp=新的SomeRoleProvider();
foreach(rp.GetRolesForUser(HttpContext.User.Identity.Name)中的var角色)
{
}
}
公共ExtController()
{
//使用此.GetType()确定此控制器是否已在字典中
if(!ControllerolesDictionary.ContainsKey(this.GetType()))
{
//如果不是,则使用反射将角色列表添加到字典中
//与控制器关联
}
}
}
这可行吗?如果是这样,我如何在ExtController构造函数中执行反射以发现[Authorize]属性和相关角色(如果有)
还有!请随意离开这个问题的范围,并提出解决“动态站点。基于角色的主菜单”问题的替代方法。我是第一个承认这可能不是最好的方法的人
编辑
经过大量的阅读和实验,我想出了自己的解决方案。下面是我的答案。欢迎任何建设性的反馈/批评 我更喜欢链接到菜单中的所有内容,并基于[Authorize]属性。好的,所以我决定像最初建议的那样充实自己的扩展控制器类。这是一个非常基本的版本。我可以看到各种各样的方法来改进它(进一步扩展、收紧代码等等),但我想我会提供我的基本结果,因为我想有很多其他人想要类似的东西,但可能不想要所有额外的东西
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”,则该控制器将没有动态菜单。(我认为,在某些情况下,这可能很有用……)
在我的Site.Master文件中,我将的“菜单”部分更改为如下所示:
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
}
}
}
<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>
就这样!:-) 我遇到了同样的问题,需要逻辑留在控制器端。但我确实喜欢John的方法,因为它使用系统过滤器来决定某个操作是否被授权。如果它对任何人都有帮助,以下代码从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;
}
我看了你的答案,请原谅我的无知,我还在学习ASP.NETMVC,但是你会把这两个类放在什么文件夹和什么文件中?类似地,SecurityTrimmedLink类是如何在Site.Master文件中使用的?请注意,在ApplicationStart上构建字典可能会更好,因为这些类在重新部署之前不会更改。然后创建菜单不是每次都是动态的,而是在字典中快速查找。