miércoles, 5 de junio de 2013
No hace demasiado tiempo describíamos por aquí cómo desacoplar controladores ASP.NET MVC mediante el uso de Inyección de Dependencias. En este caso, como en otros que hemos tratado, se hacía uso de uno de los “sabores” de esta técnica, que consiste en suministrar como parámetros al constructor de una clase todos aquellos componentes de los que depende para su funcionamiento. Sin embargo, esta no es la única forma de usar inyección de dependencias; hay otros métodos, quizás menos usados, como la inyección de propiedades o la inyección de parámetros en métodos que pueden ser interesantes en algunos casos y que, como mínimo, vale la pena conocer.
El primero de ellos consiste en hacer que el componente encargado de configurar una instancia aporte de forma automática contenido para determinadas propiedades de la clase, lo cual puede ser útil en contextos en los que no se tiene acceso directo al procedimiento de creación de la instancia; imaginad, por ejemplo, si queremos inyectar dependencias en un atributo .NET, cuya instanciación se realiza fuera de nuestro alcance.
El segundo de ellos restringe el ámbito de la dependencia a un método concreto; es decir, no se pasarán dependencias a la clase como se hace en los dos casos anteriores, sino exclusivamente al método o métodos que vayan a necesitar los componentes externos usando sus parámetros, algo así:
La implementación de esta técnica en acciones ASP.NET MVC es la que vamos a ver a lo largo de esta serie de posts. Pero ojo, que aunque a priori pueda resultar una idea atractiva y puede encajar bien en determinados escenarios, no está exenta de inconvenientes; además de hacer menos explícitas las dependencias de una clase (con la inyección en constructor son totalmente explícitas), hacen más difícil el seguimiento de principios de diseño como SRP y se puede tender a crear componentes demasiado complejos, con código duplicado, extensos, y con múltiples responsabilidades.
En cualquier caso, es una magnífica oportunidad para profundizar en la forma en que ASP.NET MVC trabaja internamente y ver lo sencillo que es su extensión una vez conocidas las bases de su funcionamiento. Por ello, veremos distintas fórmulas para conseguir nuestros objetivos:
En particular, dicha clase dispone de un método llamado
El action invoker usado por defecto en ASP.NET MVC 4 se encuentra en la clase
Bien, pero para que lo anterior funcione necesitamos aún hacer dos cosas: indicar al framework que el action invoker que debe utilizar es nuestra clase
Otra posibilidad menos intrusiva y que no nos obliga a crear un controlador base es hacer uso del propio dependency resolver de ASP.NET MVC para resolver este componente. De hecho, si observamos el código fuente del framework, podremos observar que la implementación del método
Lo más sencillo es delegar a un contenedor la gestión del ciclo de vida de las instancias, de forma que no tengamos que preocuparnos mucho por ello, tal y como se describe en este post. Aplicando a nuestro ejemplo, esto significa que nuestro dependency resolver utilizaría un contenedor IoC como Unity para obtener las instancias, y éste sería el responsable de eliminarlas al finalizar cada petición.
Podéis descargar el ejemplo completo desde mi Skydrive :-)
Publicado en: Variable not found.
El primero de ellos consiste en hacer que el componente encargado de configurar una instancia aporte de forma automática contenido para determinadas propiedades de la clase, lo cual puede ser útil en contextos en los que no se tiene acceso directo al procedimiento de creación de la instancia; imaginad, por ejemplo, si queremos inyectar dependencias en un atributo .NET, cuya instanciación se realiza fuera de nuestro alcance.
El segundo de ellos restringe el ámbito de la dependencia a un método concreto; es decir, no se pasarán dependencias a la clase como se hace en los dos casos anteriores, sino exclusivamente al método o métodos que vayan a necesitar los componentes externos usando sus parámetros, algo así:
La implementación de esta técnica en acciones ASP.NET MVC es la que vamos a ver a lo largo de esta serie de posts. Pero ojo, que aunque a priori pueda resultar una idea atractiva y puede encajar bien en determinados escenarios, no está exenta de inconvenientes; además de hacer menos explícitas las dependencias de una clase (con la inyección en constructor son totalmente explícitas), hacen más difícil el seguimiento de principios de diseño como SRP y se puede tender a crear componentes demasiado complejos, con código duplicado, extensos, y con múltiples responsabilidades.
En cualquier caso, es una magnífica oportunidad para profundizar en la forma en que ASP.NET MVC trabaja internamente y ver lo sencillo que es su extensión una vez conocidas las bases de su funcionamiento. Por ello, veremos distintas fórmulas para conseguir nuestros objetivos:
- Manualmente, usando action invokers (en este post)
- Manualmente, usando model binders
- Automáticamente, usando Autofac, un contenedor IoC que ya ofrece de serie inyección de parámetros.
Inyección de parámetros usando action invokers
Simplificando un poco, cuando ASP.NET MVC debe ejecutar una acción, hay un componente interno llamado action invoker, cuya principal implementación se encuentra en la claseControllerActionInvoker
, que se encarga de recorrer los parámetros del método y, por cada uno de ellos, crear un binder que intente proporcionar un valor o instancia para el mismo. Una vez obtenida esta información, y teniendo en cuenta otros datos del contexto como los filtros de autorización o la validación de datos de entrada, ejecutará la acción suministrando los parámetros obtenidos anteriormente, y finalmente ejecutará también el resultado devuelto.En particular, dicha clase dispone de un método llamado
GetParameterValue()
que es llamado por cada parámetro definido en la acción y retorna el valor para el mismo, por lo que es un lugar ideal para introducir la lógica de inyección que pretendemos. El action invoker usado por defecto en ASP.NET MVC 4 se encuentra en la clase
AsyncControllerActionInvoker
, que es una extensión de la base ControllerActionInvoker
. Extendiendo esta clase podemos tomar el control en el momento de obtención de los valores e introducir la lógica que nos interese; en este caso, por simplificar, usaremos la convención de que los parámetros de tipo interfaz serán resueltos de forma automática usando el dependency resolver:public class InjectionControllerActionInvoker: AsyncControllerActionInvoker { protected override object GetParameterValue( ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) { if (parameterDescriptor.ParameterType.IsInterface) { return DependencyResolver.Current .GetService(parameterDescriptor.ParameterType); } return base.GetParameterValue( controllerContext, parameterDescriptor); } }Como se observa, lo único que hacemos es sobrescribir el método
GetParameterValue()
y detectar cuándo se está intentando obtener un valor para un interfaz, retornando el resultado de intentar su resolución usando el DependencyResolver
de MVC. Por supuesto, aquí podríamos haber introducido también una llamada a nuestro contenedor IoC para realizar esta resolución. Puedes leer más sobre cómo resolver dependencias usando DependencyResolver o un contenedor IoC a partir del punto cuarto de este post.Bien, pero para que lo anterior funcione necesitamos aún hacer dos cosas: indicar al framework que el action invoker que debe utilizar es nuestra clase
InjectionControllerActionInvoker
, en lugar de la usada por defecto (ControllerActionInvoker
), y, por supuesto, configurar el dependency resolver para que sea capaz de resolver las dependencias solicitadas.¿Cómo establecer el nuevo action invoker?
Una forma de conseguir esto es desde el propio controlador, sobrescribiendo el métodoCreateActionInvoker()
que heredamos de la clase Controller
, por ejemplo así:public class ControllerBase: Controller { protected override IActionInvoker CreateActionInvoker() { return new InjectionControllerActionInvoker(); } }Esto podemos hacerlo como en el ejemplo anterior, en un controlador base del que heredaríamos todos nuestros controladores, o bien, aunque tenga menos sentido, directamente sobre los controladores donde queramos que se realice la inyección de parámetros.
Otra posibilidad menos intrusiva y que no nos obliga a crear un controlador base es hacer uso del propio dependency resolver de ASP.NET MVC para resolver este componente. De hecho, si observamos el código fuente del framework, podremos observar que la implementación del método
CreateActionInvoker()
de la clase Controller
es la siguiente:protected virtual IActionInvoker CreateActionInvoker() { return (IActionInvoker) DependencyResolverExtensions.GetService<IAsyncActionInvoker>(this.Resolver) ?? DependencyResolverExtensions.GetService<IActionInvoker>(this.Resolver) ?? (IActionInvoker) new AsyncControllerActionInvoker(); }Es decir, antes de retornar un nuevo objeto
AsyncControllerActionInvoker
, el framework solicitará una instancia al dependency resolver, por lo que bastaría con sobrescribirlo o registrar este componente en el contenedor IoC que estemos usando para tenerlo solucionado. El siguiente ejemplo simplificado muestra cómo conseguirlo en escenarios simples, sin necesidad de usar un contenedor:public class MyDependencyResolver : IDependencyResolver { public object GetService(Type serviceType) { if (serviceType == typeof (IAsyncActionInvoker)) return new InjectionControllerActionInvoker(); if (serviceType == typeof(IProductServices)) return new ProductServices(); if (serviceType == typeof(INotificationServices)) return new NotificationServices(); return null; } public IEnumerable<object> GetServices(Type serviceType) { return Enumerable.Empty<object>(); } } // // In global.asax.cs, Application_Start() // DependencyResolver.SetResolver(new MyDependencyResolver());Y de esta forma, nuestro código saldría andando. A grandes rasgos, la ejecución de llamada a la acción
Products
del controlador Home
que hemos visto al principio del post seguiría estos pasos:- Al arrancar la aplicación, en el global.asax se establece que el dependency resolver a utilizar por la aplicación será una instancia de
MyDependencyResolver
. - Una vez el framework ha determinado que debe ejecutar el método
Products()
de la claseHomeController
, llamará aCreateActionInvoker()
del controlador para obtener el componente que se encargará de invocarlo. - La implementación por defecto de
CreateActionInvoker()
llama al dependency resolver solicitándole una instancia de una clase que implemente el interfazIAsyncActionInvoker
. - Nuestro dependency resolver retorna una instancia de
InjectionControllerActionInvoker
. - El framework recorre todos los parámetros de la acción a ejecutar,
Products()
, llamando al métodoGetParameterValue()
del action invoker para obtener valor de cada uno de ellos. - El parámetro de tipo
IProductServices
, al ser de tipo interfaz, lo resolvemos desde nuestra clase action invoker llamando de nuevo al dependency resolver, al que solicitamos una instancia de dicha interfaz. MyDependencyResolver
retorna una instancia deProductServices
.- Finalmente, dado que el framework ya dispone de valores para todos los parámetros, ejecuta la acción requerida.
Lo más sencillo es delegar a un contenedor la gestión del ciclo de vida de las instancias, de forma que no tengamos que preocuparnos mucho por ello, tal y como se describe en este post. Aplicando a nuestro ejemplo, esto significa que nuestro dependency resolver utilizaría un contenedor IoC como Unity para obtener las instancias, y éste sería el responsable de eliminarlas al finalizar cada petición.
Resumen y próximos pasos
En este artículo hemos visto cómo utilizar uno de los puntos de extensibilidad de MVC, los action invokers, para inyectar parámetros a acciones ASP.NET MVC. En el siguiente post mostraremos cómo conseguir exactamente lo mismo, pero utilizando esta vez model binders, que son los componentes encargados de proporcionar valores a los parámetros de las acciones partiendo de los datos asociados a la petición actual.Podéis descargar el ejemplo completo desde mi Skydrive :-)
Publicado en: Variable not found.
2 Comentarios:
Interesante, de todas formas hecho en falta el que gano yo cambiando el action invoker. O yo me he perdido un poco o tengo la sensación que la técnica por la técnica tampoco nos trae grandes beneficions.
Hola!
No, no creo que te hayas perdido :-DDD
Como comento al principio del post, aparte de ver el propio concepto de inyección de parámetros, se trata de estudiar el funcionamiento interno del framework. Personalmente, me gusta conocer cómo funcionan por dentro las cosas con las que trabajo :)
Siendo prácticos, si te vas directamente al tercer post de la serie, tendrás una solución más directa y empaquetada a este tema.
Gracias por comentar!
Enviar un nuevo comentario