Inyectar dependencias en filtros de acción ASP.NET MVC 3. ¿Qué pasa con este enfoque?

Aquí está la configuración. Supongamos que tengo un filtro de acción que necesita una instancia de un servicio:

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

Luego tengo un ActionFilter que necesita una instancia de ese servicio:

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

En MVC 1/2 inyectar dependencias en filtros de acción fue un poco doloroso. El enfoque más común fue utilizar un invocador de acción personalizado como se puede ver aquí: http://www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action-filters/ La principal motivación detrás de esta solución fue que este siguiente enfoque se consideró descuidado y ajustado con el contenedor:

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

Aquí estamos usando inyección de constructor y sobrecargando el constructor para usar el contenedor e inyectar el servicio. Estoy de acuerdo en que se acopla el contenedor con ActionFilter.

Mi pregunta es la siguiente: ahora, en ASP.NET MVC 3, donde tenemos una abstracción del contenedor que se está utilizando (a través de DependencyResolver), ¿aún son necesarios estos aros? Permítame demostrar:

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

Ahora sé que algunos puristas podrían burlarse de esto, pero en serio, ¿cuál sería el inconveniente? Todavía se puede probar, ya que puede usar el constructor que toma un IMyService en el momento de la prueba e inyectar un servicio simulado de esa manera. No está atado a ninguna implementación de contenedor DI ya que está usando DependencyResolver, ¿hay algún inconveniente en este enfoque?

A propósito, este es otro buen enfoque para hacer esto en MVC3 usando la nueva interfaz IFilterProvider: http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in -asp-net-mvc-3

No estoy seguro, pero creo que puedes usar un constructor vacío (para la parte del atributo ) y luego tener un constructor que realmente inyecte el valor (para la parte del filtro ). *

Editar : después de leer un poco, parece que la forma aceptada de hacer esto es mediante la inyección de propiedades:

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

En cuanto a por qué no utilizar una pregunta de Localizador de servicios : en su mayoría simplemente reduce la flexibilidad de su dependency injection. Por ejemplo, ¿qué sucede si está inyectando un servicio de registro y desea darle automáticamente al servicio de registro el nombre de la clase en la que se está inyectando? Si usas la inyección de constructor, funcionaría muy bien. Si está usando un localizador de dependencia / servicio de dependencias, no tendrá suerte.

Actualizar

Como esto fue aceptado como la respuesta, me gustaría dejar constancia de que prefiero el enfoque de Mark Seeman porque separa la responsabilidad del Filtro de Acción del Atributo. Además, la extensión MVC3 de Ninject tiene algunas formas muy potentes para configurar filtros de acción a través de enlaces. Vea las siguientes referencias para más detalles:

Actualización 2

Como @usr señaló en los comentarios a continuación, ActionFilterAttribute s se ActionFilterAttribute una instancia cuando se carga la clase y dura toda la vida de la aplicación. Si se IMyService interfaz IMyService no es un Singleton, entonces termina siendo una Dependencia Cautiva . Si su implementación no es segura para subprocesos, podría sufrir mucho dolor.

Siempre que tenga una dependencia con una vida útil más corta que la esperanza de vida esperada de su clase, es aconsejable inyectar una fábrica para producir esa dependencia bajo demanda, en lugar de inyectarla directamente.

Sí, hay inconvenientes, ya que hay muchos problemas con IDependencyResolver en sí mismo, y a ellos puede agregar el uso de un Localizador de servicios Singleton , así como la inyección de Bastard .

Una mejor opción es implementar el filtro como una clase normal en la que puede inyectar los servicios que desee:

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

Observe cómo el filtro examina el filterContext para determinar si el comportamiento debe aplicarse o no.

Esto significa que aún puede usar atributos para controlar si el filtro debe aplicarse o no:

 public class MyActionFilterAttribute : Attribute { } 

Sin embargo, ahora ese atributo es completamente inerte.

El filtro puede estar compuesto con la dependencia requerida y agregado a los filtros globales en global.asax:

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

Para obtener un ejemplo más detallado de esta técnica, aunque se aplica a ASP.NET Web API en lugar de MVC, consulte este artículo: http://blog.ploeh.dk/2014/06/13/passive-attributes

La solución que Mark Seemann sugirió parece elegante. Sin embargo, es bastante complejo para un problema simple. Usar el marco implementando AuthorizeAttribute es más natural.

Mi solución fue crear un AuthorizeAttribute con una fábrica de delegates estáticos a un servicio registrado en global.asax. Funciona para cualquier contenedor DI y se siente un poco mejor que un localizador de servicio.

En global.asax:

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

Mi clase de atributo personalizado:

 [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); } } 
Intereting Posts