2010-06-08 4 views
9

Vielleicht sollte ich sichern und den Anwendungsbereich erweitern, bevor Sie in die Titelfrage eintauchen ...ASP.NET MVC: Wie finden Sie Controller mit [Authorize] -Attributen mithilfe von Reflection in C#? (oder Wie man Dynamic Site.Master Menüs erstellt?)

Ich schreibe gerade eine Webanwendung in ASP.NET MVC 1.0 (obwohl ich MVC 2.0 auf meinem PC installiert habe, also bin ich nicht genau auf 1.0 beschränkt) - Ich habe mit dem Standard MVC - Projekt begonnen, das Ihre grundlegende "Willkommen bei ASP.NET MVC" hat und beide zeigt Registerkarte [Home] und [Info] in der oberen rechten Ecke. Ziemlich Standard, oder?

Ich habe 4 neue Controller-Klassen hinzugefügt, nennen wir sie "Astronomen", "Biologen", "Chemiker" und "Physiker". An jede neue Controller-Klasse ist das [Authorize] -Attribut angehängt.

Zum Beispiel für die BiologistController.cs

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

Diese [autorisieren] Tags natürlich begrenzen, welcher Benutzer auf Rollen je verschiedene Controller zugreifen können, aber ich möchte ein Menü am oberen Rand meiner Website dynamisch bauen in der Site.Master-Seite basierend auf den Rollen, zu denen der Benutzer gehört. So zum Beispiel, wenn "JoeUser" war ein Mitglied der Rollen "Astronom" und "Physiker", würde das Navigationsmenü sagen:

[Home] [Astronom] [Physikerin] [über]

Und natürlich würde es nicht Links zu "Biologe" oder "Chemiker" Controller-Index-Seite.

Oder wenn "JohnAdmin" ein Mitglied der Rolle "Admin" war, würden Links zu allen 4 Controllern in der Navigationsleiste erscheinen.

Ok, erhalten Sie prolly die Idee ... Jetzt für die eigentliche Frage ...


mit the answer from this StackOverflow topic about Dynamic Menu building in ASP.NET starten, ich versuche zu verstehen, wie ich dies in vollem Umfang umsetzen würde. (Ich bin ein Neuling und brauche ein wenig mehr Anleitung, also bitte bare mit mir.)

Die Antwort schlägt vor, die Controller-Klasse erweitern (nennen Sie es "ExtController") und dann jede neue WhattingController erben von ExtController.

Meine Schlussfolgerung ist, dass ich Reflection in diesem ExtController-Konstruktor verwenden müsste, um zu ermitteln, welche Klassen und Methoden [Authorize] -Attribute an sie angehängt haben, um die Rollen zu bestimmen. Speichern Sie dann mit einem Static Dictionary die Rollen und Controller/Methoden in Schlüssel/Wert-Paaren.

stelle ich mir das so etwas wie:

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

Ist das machbar? Wenn ja, wie führe ich Reflection im ExtController-Konstruktor aus, um das [Authorize] -Attribut und die zugehörigen Rollen (falls vorhanden) zu finden

AUCH! Fühlen Sie sich frei, bei dieser Frage nicht in den Geltungsbereich zu kommen und schlagen Sie eine alternative Lösung vor, um dieses "Dynamic Site.Master Menu basierend auf Rollen" -Problem zu lösen. Ich bin der Erste, der zugibt, dass dies nicht der beste Ansatz ist.

EDIT

Nach viel Lesen und Experimentieren, kam ich mit meiner eigenen Lösung auf. Siehe unten für meine Antwort. Jede konstruktive Rückmeldung/Kritik erwünscht!

Antwort

3

Ok, also habe ich beschlossen, meine eigene Extended Controller-Klasse so zu gestalten, wie ich ursprünglich vorgeschlagen hatte. Hier ist eine sehr einfache Version. Ich kann verschiedene Möglichkeiten sehen, dies zu verbessern (weiter zu straffen, den Code zu straffen usw.), aber ich dachte, ich würde meine grundlegenden Ergebnisse anbieten, weil ich mir vorstelle, dass es viele andere gibt, die etwas ähnliches wollen, aber nicht alle wollen die 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,"")); 
       } 
      } 
     } 
    } 
} 

zu bedienen, einfach:

  • Fügen Sie die [autorisieren (Roles = "SomeRole1, SomeRole2, SomeRole3 usw."] An den Controller Klasse
  • Ersetzen Sie die geerbte "Controller" mit "ExtController"

. Zum Beispiel:

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

Wenn Sie "Controller" nicht durch "ExtController" ersetzen, hat dieser Controller kein dynamisches Menü. (Dies könnte nützlich sein, in einigen Szenarien, glaube ich ...)

In meinem Site.Master Datei, änderte ich den "Menü" Abschnitt wie folgt aussehen:

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

Und das ist es! :-)

+0

Als Hinweis wäre es wahrscheinlich besser, in ApplicationStart ein Dictionary zu erstellen, da sich die Klassen erst bei der erneuten Implementierung ändern. Dann ist das Erstellen des Menüs nicht immer dynamisch, sondern eine schnelle Suche in einem Wörterbuch. –

3

Ich bevorzuge die Verknüpfung zu allem in meinen Menüs und creating a HtmlHelper which checks to see if a link is accessible or not basierend auf den [Autorisieren] Attribute.

+0

Ich habe über Ihre Antwort geschaut, und bitte verzeihen Sie meine Ignoranz, ich lerne immer noch ASP.NET MVC, aber welche Ordner und welche Datei würden Sie diese 2 Klassen in der Regel stecken? Wie wird die SecurityTrimmedLink-Klasse in der Site.Master-Datei verwendet? – Pretzel