Aquí está la configuración. Digamos que tengo algún 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 molesto. El método más común era utilizar un invocador acción personalizada como se puede ver aquí: http://www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action-filters/ la La principal motivación detrás de esta solución fue porque este enfoque siguiente se consideró descuidado y un acoplamiento estrecho con el contenedor:
public class MyActionFilter : ActionFilterAttribute
{
private IMyService _myService;
public MyActionFilter()
:this(MyStaticKernel.Get<IMyService>()) //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 la inyección del constructor y sobrecargando el constructor para usar el contenedor e inyectar el servicio. Estoy de acuerdo en que acopla estrechamente el contenedor con ActionFilter.
Sin embargo, 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 del DependencyResolver), ¿siguen siendo necesarios todos 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 la desventaja? Todavía es comprobable, 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á utilizando DependencyResolver, entonces, ¿hay alguna desventaja en este enfoque?
Por cierto, aquí hay 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
Respuestas:
No estoy seguro, pero creo que puede 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 del Localizador de servicios : en su mayoría, solo reduce la flexibilidad de su inyección de dependencia. Por ejemplo, ¿qué pasaría si estuviera inyectando un servicio de registro y quisiera darle automáticamente al servicio de registro el nombre de la clase en la que se está inyectando? Si usa inyección de constructor, eso funcionaría muy bien. Si está utilizando un localizador de servicios / solucionador de dependencias, no tendrá suerte.
Actualizar
Dado que 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 poderosas de configurar filtros de acción a través de enlaces. Consulte las siguientes referencias para obtener más detalles:
Actualización 2
Como @usr señaló en los comentarios a continuación,
ActionFilterAttribute
los correos electrónicos se crean cuando se carga la clase y duran toda la vida útil de la aplicación. SiIMyService
se supone que la interfaz 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 vida útil esperada de su clase, es aconsejable inyectar una fábrica para producir esa dependencia a pedido, en lugar de inyectarla directamente.
fuente
DependencyResolver.GetService
, el método de enlace no tiene idea de en qué clase se está inyectando esta dependencia. ¿Y si quisiera crear unIMyService
filtro diferente para ciertos tipos de filtros de acción? O, como dije en mi respuesta, ¿MyService
qué pasa si quisiera proporcionar un argumento especial a la implementación para decirle en qué clase se ha inyectado (lo cual es útil para los registradores)?Sí, hay desventajas, ya que hay muchos problemas con IDependencyResolver en sí, y a ellos puede agregar el uso de un localizador de servicios Singleton , así como Bastard Injection .
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 se debe aplicar el comportamiento 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 se puede componer con la dependencia requerida y agregar 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
fuente
La solución que sugirió Mark Seemann parece elegante. Sin embargo bastante complejo para un problema simple. Usar el marco implementando AuthorizeAttribute se siente más natural.
Mi solución fue crear un AuthorizeAttribute con una fábrica de delegados estáticos a un servicio registrado en global.asax. Funciona para cualquier contenedor DI y se siente un poco mejor que un localizador de servicios.
En global.asax:
Mi clase de atributo personalizado:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class MyAuthorizeAttribute : AuthorizeAttribute { public static Func<IAuthorizeService> AuthorizeServiceFactory { get; set; } protected override bool AuthorizeCore(HttpContextBase httpContext) { return AuthorizeServiceFactory().AuthorizeCore(httpContext); } }
fuente