Iniezione di dipendenze nei filtri di azione ASP.NET MVC 3. Cosa c’è di sbagliato in questo approccio?

Ecco la configurazione. Supponiamo di avere un filtro azione che richiede un’istanza di un servizio:

public interface IMyService { void DoSomething(); } public class MyService : IMyService { public void DoSomething(){} } 

Quindi ho un ActionFilter che necessita di un’istanza di quel servizio:

 public class MyActionFilter : ActionFilterAttribute { private IMyService _myService; // <--- How do we get this injected public override void OnActionExecuting(ActionExecutingContext filterContext) { _myService.DoSomething(); base.OnActionExecuting(filterContext); } } 

In MVC 1/2 le dipendenze per iniezioni nei filtri azione erano un po ‘un rompicoglioni. L’approccio più comune è stato quello di utilizzare un invocatore di azioni personalizzate come si può vedere qui: http://www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action-filters/ The la motivazione principale alla base di questa soluzione era dovuta al fatto che il seguente approccio era considerato un accoppiamento sciatto e stretto con il contenitore:

 public class MyActionFilter : ActionFilterAttribute { private IMyService _myService; public MyActionFilter() :this(MyStaticKernel.Get()) //using Ninject, but would apply to any container { } public MyActionFilter(IMyService myService) { _myService = myService; } public override void OnActionExecuting(ActionExecutingContext filterContext) { _myService.DoSomething(); base.OnActionExecuting(filterContext); } } 

Qui stiamo usando l’iniezione del costruttore e sovraccarico il costruttore per usare il contenitore e iniettare il servizio. Sono d’accordo sul fatto che accoppiare strettamente il contenitore con ActionFilter.

La mia domanda però è questa: ora in ASP.NET MVC 3, dove abbiamo un’astrazione del contenitore utilizzato (attraverso DependencyResolver), tutti questi cerchi sono ancora necessari? Permettimi di dimostrare:

     public class MyActionFilter : ActionFilterAttribute { private IMyService _myService; public MyActionFilter() :this(DependencyResolver.Current.GetService(typeof(IMyService)) as IMyService) { } public MyActionFilter(IMyService myService) { _myService = myService; } public override void OnActionExecuting(ActionExecutingContext filterContext) { _myService.DoSomething(); base.OnActionExecuting(filterContext); } } 

    Ora so che alcuni puristi potrebbero deriderlo, ma seriamente, quale sarebbe il lato negativo? È ancora testabile poiché puoi utilizzare il costruttore che prende un IMyService in fase di test e inietta un servizio simulato in questo modo. Non sei legato a nessuna implementazione del contenitore DI, dal momento che stai utilizzando DependencyResolver, quindi ci sono degli aspetti negativi di questo approccio?

    Per inciso, ecco un altro buon approccio per farlo in MVC3 usando la nuova interfaccia IFilterProvider: http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in -Asp-net-mvc-3

    Non sono positivo, ma credo che tu possa semplicemente usare un costruttore vuoto (per la parte attributo ) e poi avere un costruttore che in realtà inietti il ​​valore (per la parte del filtro ). *

    Modifica : dopo un po ‘di lettura, sembra che il modo accettabile per farlo è tramite l’iniezione di proprietà:

     public class MyActionFilter : ActionFilterAttribute { [Injected] public IMyService MyService {get;set;} public override void OnActionExecuting(ActionExecutingContext filterContext) { MyService.DoSomething(); base.OnActionExecuting(filterContext); } } 

    Per quanto riguarda il perché non utilizzare una domanda di localizzazione del servizio : in gran parte riduce la flessibilità dell’iniezione dipendente. Ad esempio, se si stesse iniettando un servizio di registrazione e si desidera fornire automaticamente al servizio di registrazione il nome della class in cui viene iniettato? Se si utilizza l’iniezione del costruttore, funzionerebbe benissimo. Se stai usando un Dependency Resolver / Service Locator, sarai sfortunato.

    Aggiornare

    Dal momento che questo è stato accettato come risposta, mi piacerebbe andare sul disco per dire che preferisco l’approccio di Mark Seeman perché separa la responsabilità del Filtro Azione dall’attributo. Inoltre, l’estensione MVC3 di Ninject ha alcuni modi molto potenti per configurare i filtri di azione tramite bind. Vedere i seguenti riferimenti per maggiori dettagli:

    Aggiornamento 2

    Come sottolineato da @usr nei commenti di seguito, ActionFilterAttribute s viene istanziato quando la class viene caricata e dura per tutta la durata dell’applicazione. Se l’interfaccia IMyService non deve essere un Singleton, finisce per essere una dipendenza captive . Se la sua implementazione non è thread-safe, potresti soffrire molto.

    Ogni volta che si ha una dipendenza con una vita più breve rispetto alla durata prevista della propria class, è consigliabile iniettare una fabbrica per produrre tale dipendenza su richiesta, piuttosto che iniettarla direttamente.

    Sì, ci sono aspetti negativi, poiché ci sono molti problemi con IDependencyResolver stesso, e a quelli è ansible aggiungere l’uso di Singleton Service Locator, oltre a Bastard Injection .

    Un’opzione migliore è implementare il filtro come una class normale in cui è ansible iniettare qualsiasi servizio desideri:

     public class MyActionFilter : IActionFilter { private readonly IMyService myService; public MyActionFilter(IMyService myService) { this.myService = myService; } public void OnActionExecuting(ActionExecutingContext filterContext) { if(this.ApplyBehavior(filterContext)) this.myService.DoSomething(); } public void OnActionExecuted(ActionExecutedContext filterContext) { if(this.ApplyBehavior(filterContext)) this.myService.DoSomething(); } private bool ApplyBehavior(ActionExecutingContext filterContext) { // Look for a marker attribute in the filterContext or use some other rule // to determine whether or not to apply the behavior. } private bool ApplyBehavior(ActionExecutedContext filterContext) { // Same as above } } 

    Si noti come il filtro esamina il filtroContext per determinare se il comportamento debba essere applicato o meno.

    Ciò significa che puoi ancora utilizzare gli attributi per controllare se il filtro debba essere applicato o meno:

     public class MyActionFilterAttribute : Attribute { } 

    Tuttavia, ora quell’attributo è completamente inerte.

    Il filtro può essere composto con la dipendenza richiesta e aggiunto ai filtri globali in global.asax:

     GlobalFilters.Filters.Add(new MyActionFilter(new MyService())); 

    Per un esempio più dettagliato di questa tecnica, sebbene applicata all’API Web ASP.NET anziché a MVC, consulta questo articolo: http://blog.ploeh.dk/2014/06/13/passive-attributes

    La soluzione che Mark Seemann ha suggerito sembra elegante. Comunque piuttosto complesso per un semplice problema. Utilizzare il framework implementando AuthorizeAttribute è più naturale.

    La mia soluzione era creare un AuthorizeAttribute con una fabbrica di delegati statici per un servizio registrato in global.asax. Funziona per qualsiasi contenitore DI e si sente leggermente meglio di un localizzatore di servizi.

    In global.asax:

     MyAuthorizeAttribute.AuthorizeServiceFactory = () => Container.Resolve(); 

    La mia class di attributi personalizzati:

     [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class MyAuthorizeAttribute : AuthorizeAttribute { public static Func AuthorizeServiceFactory { get; set; } protected override bool AuthorizeCore(HttpContextBase httpContext) { return AuthorizeServiceFactory().AuthorizeCore(httpContext); } }