2010-06-08 10 views
9

Forse dovrei eseguire il back-up e ampliare l'ambito prima di immergermi nella domanda del titolo ...ASP.NET MVC: Come trovare i controller con gli attributi [Autorizza] utilizzando Reflection in C#? (o Come creare Dynamic Site.Master Menu?)

Attualmente sto scrivendo un'app Web in ASP.NET MVC 1.0 (anche se ho MVC 2.0 installato sul mio PC, quindi non sono esattamente limitato a 1.0) - Ho iniziato con il progetto MVC standard che ha il tuo "Welcome to ASP.NET MVC" di base e mostra sia il Scheda [Home] e scheda [Informazioni] nell'angolo in alto a destra. Abbastanza standard, giusto?

Ho aggiunto 4 nuove classi di controller, chiamiamole "Astronomo", "Biologo", "Chimico" e "Fisico". In allegato a ogni nuova classe di controller è presente l'attributo [Authorize].

Ad esempio, per i BiologistController.cs

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

Questi [Autorizza] Tag naturalmente limitare quale l'utente può accedere ai controller diversi a seconda dei ruoli, ma voglio costruire dinamicamente un menu nella parte superiore del mio sito web nella pagina Site.Master in base ai ruoli di cui l'utente fa parte. Così, per esempio, se "JoeUser" era un membro dei ruoli "Astronomo" e "Fisico", il menu di navigazione direbbe:

[Home] [Astronomo] [Fisico] [Chi]

E naturalmente, sarebbe non collegamenti elenco alla pagina indice "Biologo" o "Chemist".

O se "JohnAdmin" era un membro del ruolo "Amministratore", i collegamenti a tutti e 4 i controllori verrebbero visualizzati nella barra di navigazione.

Ok, si prolly ottiene l'idea ... Ora, per la vera domanda ...


Partendo the answer from this StackOverflow topic about Dynamic Menu building in ASP.NET, sto cercando di capire come avrei attuare pienamente questo. (Sono un principiante e ho bisogno di un po 'più di guida, quindi per favore nudi con me.)

La risposta propone Estendere la classe Controller (chiamiamola "ExtController") e poi avere ogni nuovo AnyController ereditato da ExtController.

La mia conclusione è che avrei bisogno di usare Reflection in questo Costruttore ExtController per determinare quali Classi e Metodi hanno attributi [Autorizza] ad essi associati per determinare i Ruoli. Quindi, usando un dizionario statico, memorizza i ruoli e i controllori/metodi nelle coppie chiave-valore.

immagino che qualcosa di simile:

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

È questo fattibile? In tal caso, come faccio a eseguire Reflection nel costruttore ExtController per scoprire l'attributo [Autorizza] e i relativi ruoli (se presenti)

ANCHE! Sentiti libero di andare fuori campo su questa domanda e suggerire un modo alternativo per risolvere il problema "Sito dinamico. Menu principale basato sul ruolo". Sono il primo ad ammettere che questo potrebbe non essere l'approccio migliore.

EDIT

Dopo molto la lettura e la sperimentazione, mi si avvicinò con la mia soluzione. Vedi sotto per la mia risposta. Qualsiasi commento/critica costruttiva benvenuto!

risposta

3

Ok, quindi ho deciso di arricchire la mia classe Extended Controller come inizialmente proposto. Ecco una versione molto semplice. Riesco a vedere vari modi per migliorarlo (estendere ulteriormente, stringere il codice, ecc.) Ma ho pensato di offrire i miei risultati di base perché immagino ci siano molte altre persone che vogliono qualcosa di simile, ma potrebbero non volere tutti gli extra

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

da usare, basta:

  • Aggiungere le [Autorizza (ruoli = "SomeRole1, SomeRole2, SomeRole3, ecc."] Per la classe controller
  • Sostituire il ereditato "Controller" con "ExtController"

Ad esempio:.

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

Se non si sostituisce "Controller" con "ExtController", quel Controller non avrà un menu dinamico. (Questo potrebbe essere utile, in alcuni scenari, credo ...)

Nel mio file Site.master, ho cambiato la sezione "menu" assomigliare a questo:

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

E questo è tutto! :-)

+0

Come nota, sarebbe probabilmente meglio creare un dizionario su ApplicationStart perché le classi non cambieranno fino a quando non si ridistribuisce. Quindi creare il menu non è sempre dinamico, ma una rapida ricerca in un dizionario. –

3

Preferisco il collegamento a tutti i miei menu e creating a HtmlHelper which checks to see if a link is accessible or not in base agli attributi [Autorizza].

+0

Ho esaminato la tua risposta, e per favore perdonami la mia ignoranza, sto ancora imparando ASP.NET MVC, ma quale cartella e quale file incolleresti quelle 2 classi in genere? Allo stesso modo, come viene utilizzata la classe SecurityTrimmedLink nel file Site.Master? – Pretzel

Problemi correlati