En mi opinión, usar un contenedor de IoC hoy en día, no es una opción sinó una obligación. Las ventajas que nos ofrecen son incotestables. Los patrones Service Locator y Dependency Injection nos permiten desacoplar nuestro código, y son la base para poder trabajar de forma modular y poder generar unos tests unitarios de forma más sencilla. Pero hoy no quiero hablaros de ninguno de estos patrones, sinó de otra de las capacidades de los contenedores de IoC: la generación de proxies.
Con esta técnica lo que podemos hacer es inyectar nuestro propio código para que se ejecute antes o después del código que contenga la clase en particular. Esto, si lo combinamos con los atributos nos proporciona unas capacidades potentísimas para poder tener programación orientada a aspectos.
Vamos a ver como realizar esta técnica usando Unity, pero no es exclusiva de este contenedor de IoC, otros contenedores como Windsor también tienen esta capacidad.
El mecanismo en Unity que nos permite generar proxies a partir de clases del usuario, se llama intercepción. Cuando creamos una intercepción en Unity debemos definir básicamente dos cosas:
- Qué ocurre cuando un método es interceptado (la política de intercepción).
- Cómo se intercepta un método (el interceptor).
Vamos a ver paso a paso como funciona el mecanimso.
1. Preparación del entorno
Vamos a crear una aplicación de consola, y añadimos las referencias a todos los ensamblados de Unity.
Luego vamos a crear una interfaz, y la clase que vamos a interceptar:
public interface IMyInterface
{
string SomeProperty { get; set; }
}
public class MyClass : IMyInterface
{
public string SomeProperty { get; set; }
}
Finalmente, en el método Main() creamos un contenedor de Unity y registramos el mapping entre la interfaz y el tipo:
static void Main(string[] args)
{
UnityContainer uc = new UnityContainer();
uc.RegisterType<IMyInterface, MyClass>();
}
Ahora estamos listos para empezar!!!
2. Configuración de Unity para que use un interceptor
Vamos a configurar Unity para que use un interceptor cuando se resuelva la interfaz IMyInterface. Para ello, primero debemos añadir la extensión de intercepción a Untiy y luego configurarla:
static void Main(string[] args)
{
UnityContainer uc = new UnityContainer();
uc.RegisterType<IMyInterface, MyClass>();
// Añadimos la extensión de intercepción
uc.AddNewExtension<Interception>();
// La configuramos para que nos devuelva
// un TransparentProxy cuando resolvamos IMyInterface
uc.Configure<Interception>().
SetInterceptorFor<IMyInterface>
(new TransparentProxyInterceptor());
var u = uc.Resolve<IMyInterface>();
u.SomeProperty = "test";
}
Vamos a meter un breakpoint en la última línea y a ejecutar el código, para ver si Unity ha echo algo:
Vaya… pues no parece que haya hecho nada, la verdad. La variable u es de tipo MyClass, no parece haber ningún proxy por ahí…
Es normal, ya que hemos configurado Unity para que use un TransparentProxy al resolver la interfaz IMyInterface, pero no lo hemos dicho que debe hacer Unity con este proxy, así que simplemente para no hacer nada, no crea ni el proxy…
3. Crear el interceptor
Ha llegado el momento de crear un interceptor, que defina que ocurre cuando se intercepta un método o propiedad de la clase. Para ello vamos a crear una clase nueva que implementa la interfaz ICallHandler:
public class MyHandler : ICallHandler
{
public IMethodReturn Invoke(IMethodInvocation input,
GetNextHandlerDelegate getNext)
{
IMethodReturn msg = getNext()(input, getNext);
return msg;
}
public int Order { get; set; }
}
Esta es la implementación básica por defecto de ICallHandler: no estamos haciendo nada, salvo pasar la llamada a la propiedad real de la clase. Es decir, nuestro interceptor no está haciendo realmente nada.
Podemos añadir aquí el código que queramos, p.ej:
public IMethodReturn Invoke(IMethodInvocation input,
GetNextHandlerDelegate getNext)
{
Console.WriteLine("Se ha llamado {0} con valor {1}",
input.MethodBase.Name, input.Inputs[0]);
IMethodReturn msg = getNext()(input, getNext);
return msg;
}
4. Indicar que métodos / propiedades queremos interceptar
Tenemos a Unity configurado para usar intercepción, y un interceptor creado… ahora nos queda finalmente vincular este interceptor con las propiedades o métodos que deseemos.
Para ello podemos usar los atributos: la idea es decorar cada propiedad o método con un atributo que indique que interceptor se usa para dicha propiedad y además configure dicho interceptor. Así, generalmente, vamos a usar un atributo para cada interceptor. En nuestro caso tenemos un sólo interceptor (MyHandler), así que añadiremos un atributo MyHandlerAttribute.
Para ello vamos a crear una clase que derive de HandlerAttribute (la cual a su vez deriva de Attribute), y redefinir el método CreateHandler. En este método debemos devolver el Handler que deseemos:
[AttributeUsage(AttributeTargets.Property)]
class MyHandlerAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler
(IUnityContainer container)
{
return new MyHandler();
}
}
En este caso nuestro atributo es trivial, pero en otros casos el atributo puede tener parámetros (y pasárselos al constructor del interceptor, o incluso crear un interceptor u otro en función de dichos parámetros).
5. Aplicar el atributo a las propiedades que deseemos
Para ello simplemente decoramos las propiedades (o métodos) que deseemos con el atributo:
public class MyClass : IMyInterface
{
[MyHandler]
public string SomeProperty { get; set; }
}
Y…. ya hemos terminado! Si colocamos un breakpoint en el mismo lugar de antes, veremos que ahora si que Unity nos ha creado un proxy:
Y si ejecutamos el programa, veréis como la salida por pantalla es la siguiente:
Se ha llamado set_SomeProperty con valor test |
Nuestro interceptor ha sido llamado… hemos triunfado!!! ;-)
Si añadís una propiedad extra a la interfaz (y a la clase) y NO la decoráis con el atributo veréis que, obviamente dicha propiedad NO es interceptada.
Esta técnica tiene unas posibilidades brutales… a mi se me ocurren a brote pronte, temas de logging, seguridad, validación de propiedades… vamos, todo aquello en lo que es aplicable la programación orientada a aspectos!
Un saludo a todos! ;-)
PD: Esto es un crosspost de mi blog en geeks.ms
1 comentario:
Hola, he estado trabajando con AOP y me encuentro con el siguiente limitante: que no puedo interceptar métodos estáticos. ¿Alguna sugerencua o experiencia que puedas compartir?
MIL GRACIAS :)
Publicar un comentario