Injecter des dépendances dans les filtres d’action ASP.NET MVC 3. Quel est le problème avec cette approche?

Voici la configuration. Disons que j’ai un filtre d’action qui nécessite une instance de service:

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

J’ai ensuite un ActionFilter qui a besoin d’une instance de ce service:

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

Dans MVC 1/2, l’dependency injections dans des filtres d’action était un peu douloureuse. L’approche la plus courante consistait à utiliser un invocateur d’action personnalisé, comme on peut le voir ici: http://www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action-filters/ La principale motivation de cette solution de contournement était que cette approche suivante était considérée comme un couplage négligé et serré avec le conteneur:

 public class MyActionFilter : ActionFilterAtsortingbute { 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); } } 

Ici, nous utilisons l’injection de constructeur et la surcharge du constructeur pour utiliser le conteneur et injecter le service. Je suis d’accord que cela couple étroitement le conteneur avec ActionFilter.

Ma question est la suivante: dans ASP.NET MVC 3, où nous avons une abstraction du conteneur utilisé (via DependencyResolver), est-ce que tous ces cerceaux sont encore nécessaires? Permettez-moi de démontrer:

 public class MyActionFilter : ActionFilterAtsortingbute { 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); } } 

Maintenant, je sais que certains puristes pourraient se moquer de cela, mais sérieusement, quel serait l’inconvénient? Il est toujours testable car vous pouvez utiliser le constructeur qui prend un service IMyService au moment du test et injecter un service simulé de cette façon. Vous n’êtes lié à aucune implémentation de conteneur DI, puisque vous utilisez le DependencyResolver. Cette approche présente-t-elle des inconvénients?

Incidemment, voici une autre approche intéressante pour faire cela dans MVC3 en utilisant la nouvelle interface IFilterProvider: http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in -asp-net-mvc-3

    Je ne suis pas positif, mais je pense que vous pouvez simplement utiliser un constructeur vide (pour la partie atsortingbut ) et avoir ensuite un constructeur qui injecte la valeur (pour la partie filtre ). *

    Edit : Après un peu de lecture, il semble que la manière acceptée de le faire soit par injection de propriété:

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

    En ce qui concerne le pourquoi pas utiliser une question de localisateur de services : il réduit principalement la flexibilité de votre dependency injection. Par exemple, que se passe-t-il si vous injectez un service de journalisation et que vous souhaitez atsortingbuer automatiquement au service de journalisation le nom de la classe dans laquelle il est injecté? Si vous utilisez l’injection de constructeur, cela fonctionnerait bien. Si vous utilisez un résolveur de dépendances / un localisateur de services, vous n’auriez plus de chance.

    Mettre à jour

    Étant donné que cela a été accepté comme réponse, je voudrais dire que je préfère l’approche de Mark Seeman, car elle sépare la responsabilité du filtre d’action de l’atsortingbut. De plus, l’extension MVC3 de Ninject dispose de moyens très puissants pour configurer des filtres d’action via des liaisons. Voir les références suivantes pour plus de détails:

    Mise à jour 2

    Comme @usr l’a souligné dans les commentaires ci-dessous, ActionFilterAtsortingbute s est instancié lorsque la classe est chargée et dure toute la durée de vie de l’application. Si l’interface IMyService n’est pas supposée être un Singleton, elle finit par être une dépendance captive . Si son implémentation n’est pas compatible avec les threads, vous risquez de souffrir beaucoup.

    Chaque fois que vous avez une dépendance avec une durée de vie plus courte que la durée de vie attendue de votre classe, il est sage d’injecter une usine pour produire cette dépendance à la demande, plutôt que de l’injecter directement.

    Oui, il y a des inconvénients, car il y a beaucoup de problèmes avec IDependencyResolver lui-même, et à ceux que vous pouvez append l’utilisation d’un localisateur de services Singleton , ainsi que l’ injection Bastard .

    Une meilleure option consiste à implémenter le filtre en tant que classe normale dans laquelle vous pouvez injecter les services que vous souhaitez:

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

    Notez comment le filtre examine le filterContext pour déterminer si le comportement doit être appliqué ou non.

    Cela signifie que vous pouvez toujours utiliser des atsortingbuts pour contrôler si le filtre doit être appliqué ou non:

     public class MyActionFilterAtsortingbute : Atsortingbute { } 

    Cependant, cet atsortingbut est maintenant complètement inerte.

    Le filtre peut être composé avec la dépendance requirejse et ajouté aux filtres globaux de global.asax:

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

    Pour un exemple plus détaillé de cette technique, bien que appliquée à l’API Web ASP.NET au lieu de MVC, consultez cet article: http://blog.ploeh.dk/2014/06/13/passive-atsortingbutes

    La solution suggérée par Mark Seemann semble élégante. Cependant assez complexe pour un problème simple. Utiliser le framework en implémentant AuthorizeAtsortingbute est plus naturel.

    Ma solution consistait à créer un AuthorizeAtsortingbute avec une fabrique de delegates statique sur un service enregistré dans global.asax. Cela fonctionne pour n’importe quel conteneur DI et se sent légèrement mieux qu’un localisateur de service.

    Dans global.asax:

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

    Ma classe d’atsortingbuts personnalisés:

     [AtsortingbuteUsage(AtsortingbuteTargets.Method | AtsortingbuteTargets.Class)] public class MyAuthorizeAtsortingbute : AuthorizeAtsortingbute { public static Func AuthorizeServiceFactory { get; set; } protected override bool AuthorizeCore(HttpContextBase httpContext) { return AuthorizeServiceFactory().AuthorizeCore(httpContext); } }