2011-01-13 9 views
23

Ho eseguito diversi casi in ASP.NET MVC in cui volevo applicare un filtro azione su ogni azione tranne uno o due. Ad esempio, supponiamo tu abbia un AccountController. Ogni azione in esso richiede l'accesso dell'utente, quindi aggiungi [Autorizza] a livello di controller. Ma dì che vuoi includere la pagina di login in AccountController. Il problema è che gli utenti inviati alla pagina di accesso non sono autorizzati, quindi ciò comporterebbe un ciclo infinito.Un modo per escludere i filtri di azione in ASP.NET MVC?

La correzione ovvia (diversa dallo spostamento dell'azione di accesso su un altro controller) consiste nel spostare [Autorizza] dal controller in tutti i metodi di azione, ad eccezione dell'accesso. Beh, non è divertente, soprattutto quando hai molti metodi o dimentichi di aggiungere [Autorizza] a un nuovo metodo.

Rails rende questo facile con la possibilità di escludere i filtri. ASP.NET MVC non ti lascia. Così ho deciso di renderlo possibile ed è stato più facile di quanto pensassi.

/// <summary> 
/// This will disable any filters of the given type from being applied. This is useful when, say, all but on action need the Authorize filter. 
/// </summary> 
[AttributeUsage(AttributeTargets.Method|AttributeTargets.Class, AllowMultiple=true)] 
public class ExcludeFilterAttribute : ActionFilterAttribute 
{ 

    public ExcludeFilterAttribute(Type toExclude) 
    { 
     FilterToExclude = toExclude; 
    } 

    /// <summary> 
    /// The type of filter that will be ignored. 
    /// </summary> 
    public Type FilterToExclude 
    { 
     get; 
     private set; 
    } 
} 

/// <summary> 
/// A subclass of ControllerActionInvoker that implements the functionality of IgnoreFilterAttribute. To use this, just override Controller.CreateActionInvoker() and return an instance of this. 
/// </summary> 
public class ControllerActionInvokerWithExcludeFilter : ControllerActionInvoker 
{ 
    protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) 
    { 
     //base implementation does all the hard work. we just prune off the filters to ignore 
     var filterInfo = base.GetFilters(controllerContext, actionDescriptor);   
     foreach(var toExclude in filterInfo.ActionFilters.OfType<ExcludeFilterAttribute>().Select(f=>f.FilterToExclude).ToArray()) 
     { 
      RemoveWhere(filterInfo.ActionFilters, filter => toExclude.IsAssignableFrom(filter.GetType())); 
      RemoveWhere(filterInfo.AuthorizationFilters, filter => toExclude.IsAssignableFrom(filter.GetType())); 
      RemoveWhere(filterInfo.ExceptionFilters, filter => toExclude.IsAssignableFrom(filter.GetType())); 
      RemoveWhere(filterInfo.ResultFilters, filter => toExclude.IsAssignableFrom(filter.GetType())); 
     } 
     return filterInfo; 
    } 


    /// <summary> 
    /// Removes all elements from the list that satisfy the condition. Returns the list that was passed in (minus removed elements) for chaining. Ripped from one of my helper libraries (where it was a pretty extension method). 
    /// </summary> 
    private static IList<T> RemoveWhere<T>(IList<T> list, Predicate<T> predicate) 
    { 

     if (list == null || list.Count == 0) 
      return list; 
     //note: didn't use foreach because an exception will be thrown when you remove items during enumeration 
     for (var i = 0; i < list.Count; i++) 
     { 
      var item = list[i]; 
      if (predicate(item)) 
      { 
       list.RemoveAt(i); 
       i--; 
      } 
     } 
     return list; 
    } 
} 

/// <summary> 
/// An example of using the ExcludeFilterAttribute. In this case, Action1 and Action3 require authorization but not Action2. Notice the CreateActionInvoker() override. That's necessary for the attribute to work and is probably best to put in some base class. 
/// </summary> 
[Authorize] 
public class ExampleController : Controller 
{ 
    protected override IActionInvoker CreateActionInvoker() 
    { 
     return new ControllerActionInvokerWithExcludeFilter(); 
    } 

    public ActionResult Action1() 
    { 
     return View(); 
    } 

    [ExcludeFilter(typeof(AuthorizeAttribute))] 
    public ActionResult Action2() 
    { 
     return View(); 
    } 

    public ActionResult Action3() 
    { 
     return View(); 
    } 

} 

L'esempio è proprio lì. Come puoi vedere, questo è stato abbastanza semplice da fare e funziona alla grande. Spero sia utile a qualcuno?

+0

'Elenco .RemoveAll' esiste: http://msdn.microsoft.com/en-us/library/wdka673a.aspx –

+0

Sì, so su List.RemoveAll. Il problema è System.Web.Mvc.FilterInfo espone queste raccolte come IList <> e non List , anche se l'implementazione sottostante è Elenco <>. Avrei potuto scegliere l'elenco e usare RemoveAll, ma ho ritenuto che fosse meglio rispettare l'API. Il mio piccolo metodo di aiuto è un po 'brutto, sì. Normalmente ho quello nascosto in una libreria di supporto come metodo di estensione, il che rende il codice molto più pulito. Ma per questo volevo che fosse compilato tramite copia incolla. Cosa ne pensi? –

+0

Un altro modo per escludere un filtro esistente è l'implementazione di IFilterProvider. Guarda l'esempio completo qui: http://blogs.microsoft.co.il/blogs/oric/archive/2011/10/28/exclude-a-filter.aspx –

risposta

23

Preferisco la soluzione delineata here. Sebbene non sia una soluzione generica come la tua, l'ho trovata un po 'più semplice.

Nel mio caso, stavo cercando un modo per abilitare un CompressionFilter su tutto tranne alcuni elementi. Così ho creato un attributo vuoto in questo modo:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 
public sealed class DisableCompression : Attribute { } 

Poi, nel principale attributo, verificare la presenza dell'attributo in questo modo:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] 
public class CompressionFilter : ActionFilterAttribute 
{ 
    public override void OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     bool disabled = filterContext.ActionDescriptor.IsDefined(typeof(DisableCompression), true) || 
         filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(DisableCompression), true); 
     if (disabled) 
      return; 

     // action filter logic here... 
    } 
} 

se la pagina ho collegato a menziona il fatto che questo è per MVC 3, sembra funzionare abbastanza bene anche in MVC 1.

MODIFICA: mostra un po 'di utilizzo qui in risposta ai commenti. Prima ho apportato le modifiche sopra, sembrava esattamente come questo, tranne senza l'attributo [DisableCompression] che indicava il metodo che volevo escludere. Non sono coinvolti altri refactoring.

[CompressionFilter] 
public abstract class BaseController : Controller 
{ 
} 

public class SomeController : BaseController 
{ 
    public ActionResult WantThisActionCompressed() 
    { 
     // code 
    } 

    [DisableCompression] 
    public ActionResult DontWantThisActionCompressed() 
    { 
     // code 
    } 
} 
+0

Per ogni tipo di attributo che desideri disabilitare, devi creare un nuovo " disattiva l'attributo e modifica l'attributo originale e assicurati di sostituire tutti i casi di quell'attributo nel tuo codice. Sembra un sacco di lavoro ingombrante rispetto alla mia soluzione, che non richiede codice aggiuntivo. Come sviluppatore che crede in DRY, non vedo come qualcuno possa vedere la tua soluzione come migliore. L'unico vantaggio che vedo è che è più esplicito. Ma allora cosa? –

+0

Mi piace l'esplicito perché è più facile per me e altri sviluppatori capire. E realisticamente, il numero di filtri di azione che uno utilizza in una singola app Web, che deve essere applicato anche a tutti tranne alcune azioni, è sicuramente molto basso. E 4 linee extra di codice non mi sembrano complicate. Non sai da dove tieni tutti gli altri ingombranti, per quanto riguarda l'uso funziona praticamente sullo stesso principio del tuo. – Gavin

+0

Quindi, per disabilitare l'attributo [Autorizza], è necessario sottoclasse [Autorizza] in qualcosa come [DisableableAuthorize], quindi creane uno nuovo chiamato [DisableAuthorize]. POI devi sostituire tutti i casi di [Autorizza] nella tua app con [DisableableAuthorize] e assicurati che tutti si ricordino di usare [DisableableAuthorize]. Sembra un incubo di manutenzione e due nuove classi che potrebbero essere evitate. E come hai detto tu, il numero di volte che devi disabilitare gli attributi è pochi. Quindi perché passare attraverso tutti questi problemi? L'attributo [ExcludeFilter] è rapido e semplice, se usato una sola volta. –

0

presumo per anni fa, che l'attributo [AllowAnnonymous] non era stato aggiunto alla ASP.NET MVC. Oggi posso avere l'attributo [Authorize] in cima al mio controller che si applica a tutti i metodi di azione e semplicemente lo sovrascrivo in Azioni che richiedono utenti non autorizzati aggiungendo gli attributi [AllowAnonymous] alle azioni specifiche.

Problemi correlati