2012-05-22 9 views
11

Ich habe eine Situation, in der ich einige Abhängigkeiten in einem Aktionsfilter einspeisen muss, nämlich meinen benutzerdefinierten Autorisierungsanbieter in meinem benutzerdefinierten Autorisierungsattribut. Ich stolperte über viele Leute und Beiträge, die sagten, dass wir die "Attribut-Metadaten" vom "Verhalten" trennen sollten. Dies ist sinnvoll und es gibt auch die Tatsache, dass Filterattribute nicht durch den 'DependencyResolver' instanziiert werden, so dass es schwierig ist, die Abhängigkeiten zu injizieren.IFilterProvider und Trennung von Bedenken

Also habe ich ein wenig Refactoring meines Codes gemacht und ich wollte wissen, ob ich es richtig gemacht habe (ich benutze Castle Windsor als das DI-Framework).

Zunächst einmal zog ich mein Attribut nur die Rohdaten enthalten Ich brauche

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 
public class MyAuthorizeAttribute : Attribute 
{ 
    public string Code { get; set; } 
} 

ich eine benutzerdefinierte Berechtigungs Filter erzeugt, der die Logik der Bestimmung, ob der aktuelle Benutzer über die Berechtigung

public class MyAuthorizationFilter : IAuthorizationFilter 
{ 
    private IAuthorizationProvider _authorizationProvider; 
    private string _code; 

    public MyAuthorizationFilter(IAuthorizationProvider authorizationProvider, string code) 
    { 
     Contract.Requires(authorizationProvider != null); 
     Contract.Requires(!string.IsNullOrWhiteSpace(code)); 

     _authorizationProvider = authorizationProvider; 
     _code = code; 
    } 

    public void OnAuthorization(AuthorizationContext filterContext) 
    { 
     if (filterContext == null) 
     { 
      throw new ArgumentNullException("filterContext"); 
     } 

     if (filterContext.HttpContext.Request.IsAuthenticated) 
     { 
      BaseController controller = filterContext.Controller as BaseController; 
      if (controller != null) 
      { 
       if (!IsAuthorized(controller.CurrentUser, controller.GetCurrentSecurityContext())) 
       { 
        // forbidden 
        filterContext.RequestContext.HttpContext.Response.StatusCode = 403; 
        if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest()) 
        { 
         filterContext.Result = new RedirectToRouteResult("default", new RouteValueDictionary(new 
         { 
          action = "http403", 
          controller = "error" 
         }), false); 
        } 
        else 
        { 
         filterContext.Result = controller.InvokeHttp404(filterContext.HttpContext); 
        } 
       } 
      } 
      else 
      { 

      } 
     } 
     else 
     { 
      filterContext.Result = new RedirectResult(FormsAuthentication.LoginUrl); 
     } 
    } 

    private bool IsAuthorized(MyUser user, BaseSecurityContext securityContext) 
    { 
     bool has = false; 
     if (_authorizationProvider != null && !string.IsNullOrWhiteSpace(_code)) 
     { 
      if (user != null) 
      { 
       if (securityContext != null) 
       { 
        has = _authorizationProvider.HasPermission(user, _code, securityContext); 
       } 
      } 
     } 
     else 
     { 
      has = true; 
     } 
     return has; 
    } 
} 
enthalten würde

Der letzte Teil bestand darin, einen benutzerdefinierten Filteranbieter zu erstellen, der dieses spezifische Attribut abrufen und meinen benutzerdefinierten Filter instanziieren würde, indem er seine Abhängigkeiten und alle Daten, die er benötigt, aus dem Attribut extrahierte.

public class MyAuthorizationFilterProvider : IFilterProvider 
{ 
    private IWindsorContainer _container; 

    public MyAuthorizationFilterProvider(IWindsorContainer container) 
    { 
     Contract.Requires(container != null); 
     _container = container; 
    } 

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) 
    { 
     Type controllerType = controllerContext.Controller.GetType(); 
     var authorizationProvider = _container.Resolve<IAuthorizationProvider>(); 
     foreach (MyAuthorizeAttribute attribute in controllerType.GetCustomAttributes(typeof(MyAuthorizeAttribute), false)) 
     { 
      yield return new Filter(new MyAuthorizationFilter(authorizationProvider, attribute.Code), FilterScope.Controller, 0); 
     } 
     foreach (MyAuthorizeAttribute attribute in actionDescriptor.GetCustomAttributes(typeof(MyAuthorizeAttribute), false)) 
     { 
      yield return new Filter(new MyAuthorizationFilter(authorizationProvider, attribute.Code), FilterScope.Action, 0); 
     } 
    } 
} 

Der letzte Schritt ist der der Filteranbieter im

global.asax registrieren
FilterProviders.Providers.Add(new MyAuthorizationFilterProvider(_container)); 

Also zuerst frage ich mich, wenn ich auf die Idee richtig und zweite bekam, was verbessert werden könnte.

+0

Hallo Francois, ich kam mit einer ziemlich ähnlichen Lösung für das gleiche Problem wie Sie. Ich stelle mir gerade die gleichen Fragen wie Sie. Haben Sie am Ende diese Lösung verwendet? Irgendein Problem damit im Laufe der Zeit? Hast du eine Empfehlung? Vielen Dank. –

Antwort

2

Ja, ich denke, Sie haben die Idee richtig verstanden. Ich mag, dass Sie Bedenken zwischen dem Attribut und der Filterimplementierung trennen, und ich mag, dass Sie Konstruktor DI lieber als Eigenschaft DI verwenden.

Ihr Ansatz funktioniert gut, wenn Sie nur einen Filtertyp haben. Ich denke, der größte potenzielle Bereich für Verbesserungen wäre, wenn Sie mehr als einen Filtertyp hätten, die Implementierung des Filteranbieters. Derzeit ist der Filteranbieter eng mit den Attribut- und Filterinstanzen verbunden, die er bereitstellt.

Wenn Sie das Attribut mit dem Filter kombinieren und die Eigenschaft DI verwenden möchten, gibt es eine einfache Möglichkeit, einen entkoppelten Filteranbieter zu haben. Hier sind zwei Beispiele für diesen Ansatz: http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in-asp-net-mvc-3 http://lozanotek.com/blog/archive/2010/10/12/dependency_injection_for_filters_in_mvc3.aspx

Es gibt zwei Herausforderungen mit dem aktuellen Ansatz zu lösen: 1. einige Spritzen, aber nicht alle der Filter Konstruktorparameter über DI. 2. Zuordnung von einem Attribut zu einer (Abhängigkeits-injizierten) Filterinstanz.

Zur Zeit tun Sie beide manuell, was sicherlich gut ist, wenn nur ein Filter/Attribut vorhanden ist. Wenn es mehr gäbe, würden Sie wahrscheinlich eine allgemeinere Annäherung für beide Teile wünschen.

Für die Herausforderung # 1 könnten Sie etwas wie eine _container.Resolve-Überladung verwenden, mit der Sie Argumente übergeben können. Diese Lösung ist eher containerspezifisch und wahrscheinlich etwas knifflig.

Eine andere Lösung, die ich hier beschreiben werde, trennt eine Factory-Klasse, die nur Abhängigkeiten in ihrem Konstruktor akzeptiert und eine Filterinstanz erzeugt, die sowohl DI- als auch Nicht-DI-Argumente benötigt.

Hier ist, was die Fabrik aussehen könnte:

public class MyAuthorizationFilterFactory : IFilterInstanceFactory 
{ 
    private readonly IAuthorizationProvider provider; 

    public MyAuthorizationFilterFactory(IAuthorizationProvider provider) 
    { 
     this.provider = provider; 
    } 

    public object Create(Attribute attribute) 
    { 
     MyAuthorizeAttribute authorizeAttribute = attribute as MyAuthorizeAttribute; 

     if (authorizeAttribute == null) 
     { 
      return null; 
     } 

     return new MyAuthorizationFilter(provider, authorizeAttribute.Code); 
    } 
} 

Sie lösen 2 # Herausforderung jeder nur durch Registrierung:

public interface IFilterInstanceFactory 
{ 
    object Create(Attribute attribute); 
} 

Sie dann eine Fabrik für jedes Attribut/Filterpaar implementieren würde Implementierung von IFilterInstanceFactory mit CastleWindsor.

kann der Filter-Anbieter nun von jeder Kenntnis der spezifischen Eigenschaften und Filter entkoppelt werden: durch David

public class MyFilterProvider : IFilterProvider 
{ 
    private IWindsorContainer _container; 

    public MyFilterProvider(IWindsorContainer container) 
    { 
     Contract.Requires(container != null); 
     _container = container; 
    } 

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) 
    { 
     Type controllerType = controllerContext.Controller.GetType(); 
     var authorizationProvider = _container.Resolve<IAuthorizationProvider>(); 
     foreach (FilterAttribute attribute in controllerType.GetCustomAttributes(typeof(FilterAttribute), false)) 
     { 
      object instance = Resolve(attribute); 
      yield return new Filter(instance, FilterScope.Controller, 0); 
     } 
     foreach (FilterAttribute attribute in actionDescriptor.GetCustomAttributes(typeof(FilterAttribute), false)) 
     { 
      object instance = Resolve(attribute); 
      yield return new Filter(instance, FilterScope.Action, 0); 
     } 
    } 

    private object Resolve(Attribute attribute) 
    { 
     IFilterInstanceFactory[] factories = _container.ResolveAll<IFilterInstanceFactory>(); 

     foreach (IFilterInstanceFactory factory in factories) 
     { 
      object dependencyInjectedInstance = factory.Create(attribute); 

      if (dependencyInjectedInstance != null) 
      { 
       return dependencyInjectedInstance; 
      } 
     } 

     return attribute; 
    } 
} 

David

+0

Vermutlich fehlt etwas, aber dieser obige Code gibt tatsächlich keine Instanz des Attributs zurück (nicht den zugehörigen Filter). 'code' Objektinstanz = Resolve (Attribut); Ertrag Rückkehr neuer Filter (Instanz, FilterScope.Action, 0); 'Code' –

+0

Doh, ignoriere mich, lesen Sie erneut und erkannte, dass die Fabrik die Filtererstellung behandelt. –

0

Dies ist wahrscheinlich ein bisschen viel, aber eine Möglichkeit zur Vermeidung der Fabrik wie vorgeschlagen (und dies etwas generischer zu machen) soll noch ein weiteres Attribut einführen.

[AssociatedFilter(typeof(MyAuthorizationFilter))] 

Die Sie zum ursprünglichen Attribut wie folgt hinzufügen könnten.

[AssociatedFilter(typeof(MyAuthorizationFilter))] 
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 
public class MyAuthorizeAttribute : Attribute 
{ 
    public string Code { get; set; } 
} 

Das AssociatedFilter-Attribut sieht so aus.

Dann können Sie den richtigen Filter abrufen, indem Sie den FilterType aus diesem Attribut herausziehen.

private object Resolve(Attribute attribute) 
{ 
    var filterAttributes = attribute.GetType().GetCustomAttributes(typeof(AssociatedFilterAttribute), false); 
    var first = (AssociatedFilterAttribute)filterAttributes.FirstOrDefault(); 
    return new Filter(_container.Resolve(first.FilterType), FilterScope.First, null); 
} 

Derzeit ist dies beschränkt sich auf nur das erste AssociatedFilter Attribut nehmen, theoretisch ich denke, man mehr als man hinzufügen könnte (ein Attribut mehrere Filter startet), in dem Fall, dass Sie das Bit weglassen würde, wo dies der erste Greifer Ergebnis.

Offensichtlich müssen wir auch Fehlerbehandlung hinzufügen, z. Wenn es kein AssociatedFilterAttribute gibt ...

Verwandte Themen