Cómo hemos visto en un post anterior, es muy sencillo configurar la inyección de dependencias con Unity en ASP.NET MVC y Web Api instalando los paquetes de nuget correspondientes. Sin embargo, aún nos quedan pasos que dar para conseguir una inyección de dependencias completa.

En ASP.NET MVC y Web Api podemos añadir comportamientos a los controllers y sus actions mediante el uso de Filtros. De esta forma podemos añadir verticales, como autorización, logging u otros, sin tener que implementarlos de forma repetitiva en cada controlador, o sin crear un controller base.

Por lo general tenemos dos formas de añadir filtros, mediante atributos o añadiéndolos a la colección GlobalFilters (o GlobalConfiguration.Configuration.Filters en Web Api), según el ámbito al que queremos se aplique el comportamiento.

Por ejemplo, podríamos añadir un filtro sencillo de la siguiente forma:

Para un filtro simple nos serviría, sin embargo cuando tenemos filtros más complejos, para autorización u otros comportamientos, es muy probable que tengan dependencias sobre varios de nuestros colaboradores, por ejemplo:

Esta implementación presenta las siguientes limitaciones:

  1. Tenemos que crear todos los colaboradores de forma manual, lo que en escenarios más complejos aumenta los costes de mantenimiento.
  2. Estamos creando las instancias al inicio de nuestra aplicación, por lo que tanto el filtro como sus colaboradores se convierten en singleton que atenderán a todas las peticiones. Esto nos fuerza a que el filtro y sus colaboradores tengan que ser ThreadSafe ya que la misma instancia responderá a múltiples peticiones simultáneas, por tanto es necesario que pueda trabajar en multihilo.
  3. Cuando usamos filtros como atributos de un controller o action tendríamos que pasar las instancias en el momento de declarar el atributo, ya que es .NET el que se encarga de instanciar los atributos.
Diseñar una clase para que sea ThreadSafe puede no parecer complicado a simple vista, sin embargo sus problemas no son aparentes a simple vista y en muchas ocasiones no se reproducen hasta que tenemos la aplicación en producción. Es muy probable que tengamos estos problemas si nuestro filtro necesita acceder a la base de datos y estamos utilizando Entity Framework para el acceso a datos, pronto escribiré un post sobre los problemas que esto puede ocasionar.
 

Proveedores de Filtros

Ya estamos realizando inyección de dependencias con ASP.NET y MVC, gracias a los paquestes de Unity para ambos, ¿no podemos utilizarla para los filtros?.

Tanto en MVC como en WebApi, podemos utilizar la interfaz IFilterProvider para interceptar las solicitudes de filtros e integrarlos con el framework IOC que elijamos.

Para los filtros declarados como atributos, en el paquete de Nuget de Unity para MVC tenemos la clase UnityFilterAttributeFilterProvider. En el caso de Web Api tendremos  que crear nuestra implementación de IFilterProvider:

Sólo tenemos que registrar los nuevos proveedores, de la siguiente forma:

[thb_tabs] [thb_tab title=”MVC”] [/thb_tab] [thb_tab title=”Web API”] [/thb_tab] [/thb_tabs]

De esta forma tenemos un proveedor de filtros que inyecta las dependencias de los filtros declarados como atributos. Para los filtros globales, sólo tendríamos que crear una implementación de IFilterProvider que retorne aquellos filtros globales, y reemplazar la implementación por defecto del mismo modo que el ejemplo anterior.

No estamos ahí… todavía

Mediante los IFilterProvider hemos podido inyectar las dependencias de nuestros filtros. Sin embargo esta solución continúa teniendo limitaciones.

Primero, nos obliga a declarar de forma diferente las dependencias según si el filtro va a ser usado como atributo o no, ya que en un atributo nos fuerza a declarar nuestras dependencias como propiedades. De este modo estamos asociando la implementación al uso que se le dará posteriormente, si varía el uso en un futuro tendremos que cambiar la implementación.

Desde mi punto de vista, cuando declaramos una dependencia con un colaborador, lo hacemos a través del constructor porque esta es obligatoria y por tanto la instancia no puede ser construida si ese colaborador falta. Esto nos obliga a documentar correctamente qué propiedades son obligatorias para construir la instancia correctamente, ya que no tenemos forma de conocerlas.

Por otro lado, aunque en MVC esta implementación cumpliría perfectamente con la segunda limitación, no lo hace en WebAPI, ya que éste último cachea todas las instancias devueltas por los FilterProvider, teniendo de nuevo un Singleton, esta vez por acción o controlador.

¿Podemos encontrar una implementación mejor que solucione todas las limitaciones?

Wrapper Filters

Una posible solución, es crear una wrapper por cada tipo de filtro que necesitamos, el cuál determina cuando se debe crear una instancia de nuestro filtros y delega la ejecución a éstos últimos. Por ejemplo si queremos inyectar filtros para un Action, tendremos el siguiente wrapper:

Sólo expongo los ejemplos para ASP.NET MVC, la implementación de para Web Api es idéntica, sólo es necesario cambiar el espacio de nombres System.Web.MVC por System.Web.Http

En este caso, creamos un ActionFilter, el cuál recibe el tipo del filtro final que se utilizará. Cómo vimos en el post anterior, aprovechamos que hemos configurado Unity para devolver instancias diferentes en cada Request, por lo que sólo necesitamos solicitar la instancia al contenedor, y él se encarga de la gestión del tiempo de vida.

Hemos implementado el wrapper como un atributo global para poder así aplicarlo tanto de forma global, como en un controller o action específico:

Para cada filtro que necesitemos, tenemos dos pasos que realizar,  primero registrar el filtro real en el contenedor IOC especificando el tiempo de vida que utilizaremos, y segundo, registrarlo como filtro global o declararlo en el controller o action que necesitemos.

En el caso de un filtro global, podemos intentar reducir estos pasos con un simple método de extensión:

[thb_tabs] [thb_tab title=”MVC”] [/thb_tab] [thb_tab title=”Web API”] [/thb_tab] [/thb_tabs]

De esta forma podemos registrar nuestros filtros globales con una única llamada:

Conclusión

Esta implementación nos permite inyectar las dependencias de nuestros filtros, manteniendo la configuración de ciclo de vida que tengamos en nuestro contenedor de inyección de dependencias, y crear los filtros de forma independiente a cómo serán usados posteriormente, de forma global o cómo atributos.

Nos leemos!