Pre*_*zel 9 c# asp.net reflection asp.net-mvc authorize-attribute
也许在进入标题问题之前我应该备份并扩大范围......
我目前正在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(); }
}
Run Code Online (Sandbox Code Playgroud)
这些[授权]标签自然限制哪些用户可以根据角色访问不同的控制器,但我想根据用户所属的角色在Site.Master页面的网站顶部动态构建一个菜单.例如,如果"JoeUser"是角色"天文学家"和"物理学家"的成员,导航菜单会说:
[主页] [天文学家] [物理学家] [关于]
当然,它不会列出"生物学家"或"化学家"控制器索引页面的链接.
或者,如果"JohnAdmin"是角色"Admin"的成员,则所有4个控制器的链接将显示在导航栏中.
好吧,你们大家都有了想法......现在回答真正的问题......
从这个关于ASP.NET中动态菜单构建的StackOverflow主题的答案开始,我试图理解如何完全实现它.(我是新手,需要更多指导,所以请和我一起去.)
答案建议扩展Controller类(称之为"ExtController"),然后让每个新的WhateverController继承自ExtController.
我的结论是我需要在这个ExtController构造函数中使用Reflection来确定哪些类和方法附加了[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
}
}
}
Run Code Online (Sandbox Code Playgroud)
这可行吗?如果是这样,我如何在ExtController构造函数中执行Reflection来发现[Authorize]属性和相关角色(如果有的话)
也!在这个问题上随意超出范围并建议另一种方法来解决这个"基于角色的动态Site.Master菜单"问题.我是第一个承认这可能不是最佳方法的人.
编辑
经过大量的阅读和实验,我想出了自己的解决方案.请参阅下面的答案.任何建设性的反馈/批评欢迎!
好的,所以我决定像我最初提议的那样充实我自己的扩展控制器类。这是一个非常基本的版本。我可以看到各种让它变得更好的方法(进一步扩展,收紧代码等),但我想我会提供我的基本结果,因为我想还有很多其他人想要类似的东西,但可能不想要全部额外的。
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,""));
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用时,只需:
例如:
[Authorize(Roles = "Biologist,Admin")]
public class BiologistController : ExtController
{
public ActionResult Index()
{ return View(); }
}
Run Code Online (Sandbox Code Playgroud)
如果您不将“Controller”替换为“ExtController”,则该控制器将不会有动态菜单。(我认为这在某些情况下可能很有用......)
在我的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>
Run Code Online (Sandbox Code Playgroud)
就是这样!:-)