Ninject. Configurar interceptors

¡Hola!
En la anterior entrada vimos cómo configurar nuestro contenedor a través de un archivo XML, hoy vamos a ver cómo añadir interceptors y así crear decoradores sin tener que declararlos explícitamente.
Nos vamos a imaginar que queremos dejar una traza de todos los métodos que se invocan de un servicio en concreto. Esto, como decíamos, lo podríamos hacer creando una capa por encima (un decorator), pero vamos a ver cómo hacerlo de forma sencilla con Ninject a través de un interceptor. Además de Ninject, también vamos a hacer uso de Castle.Core. Agregamos a nuestro proyecto a través de NuGet Ninject y Ninject.Extensions.Interception.DynamicProxy (este último nos agregará varias referencias, entre ellas Castle.Core para utilizar Dinamic Proxy):

En la imagen están resaltados los dos paquetes que hemos añadido, los otros dos son dependencias que se han añadido con Ninject.Extensions.Interception.DinamicProxy.

Ahora vamos a pensar que tenemos una interfaz y un servicio cualquiera que la implementa.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public interface IDummyService
    {
        void DummyMethod();
        void OtherDummyMethod(string parameter);
    }
 
    public class DummyService : IDummyService
    {
        public void DummyMethod()
        {
            Console.WriteLine("Ejecutando DummyMethod");
        }
 
        public void OtherDummyMethod(string parameter)
        {
            Console.WriteLine($"Ejecutando OtherDummyMethod");
        }
    }

Ahora vamos a crear nuestro interceptor que registrará la entrada y salida del método. Utilizaremos una clase abstracta que nos proporciona Ninject que se llama SimpleInterceptor, aunque podríamos crear una desde cero que implemente IInterceptor. El código quedaría así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public class TraceInterceptor : SimpleInterceptor
    {
        protected override void BeforeInvoke(IInvocation invocation)
        {
            var trace = $"Método {invocation.Request.Method.Name} de la clase {invocation.Request.Target}" +
                        $" capturado ANTES DE INVOCARSE. Tiene {invocation.Request.Arguments.Length} parámetros";
 
            Console.WriteLine(trace);
        }
 
        protected override void AfterInvoke(IInvocation invocation)
        {
            var trace = $"Método {invocation.Request.Method.Name} de la clase {invocation.Request.Target}" +
                        $" capturado DESPUÉS DE INVOCARSE. Tiene {invocation.Request.Arguments.Length} parámetros";
 
            Console.WriteLine(trace);
        }
    }

En el caso de SimpleInterceptor no es necesario invocar explícitamente la llamada al método real (se encarga él). Si quisiéramos hacerlo de forma manual se haría con invocation.Proceed(). Dentro del parámetro invocation hay un montón de información a la que podemos acceder referente a los parámetros, el método invocado, la configuración de Ninject, etc. Si en el constructor de TraceInterceptor tuviésemos que resolver alguna dependencia (ITraceService por ejemplo), siempre y cuando esté definida en el contenedor con Bind<>().To<>() por ejemplo, se resolvería por Ninject sin problema.
Ahora, en nuestro contenedor, vamos a indicar que para la resolución de la interfaz IDummyService se utilice nuestro interceptor llamado ITraceInterceptor:

1
2
3
4
5
6
7
    public class Composer: NinjectModule
    {
        public override void Load()
        {
            this.Bind<IDummyService>().To<DummyService>().Intercept().With<TraceInterceptor>();
        }
    }

Finalmente vamos a ponerlo en marcha todo a ver qué ocurre:

1
2
3
4
5
6
7
8
9
10
11
12
    class Program
    {
        static void Main(string[] args)
        {
            var composer = new StandardKernel(new Composer());
            var dummyService = composer.Get<IDummyService>();
            dummyService.DummyMethod();
            Console.WriteLine("---------------------------------------------");
            dummyService.OtherDummyMethod("prueba");
            Console.ReadLine();
        }
    }

Y el resultado es….
¿Qué ha pasado? Pues lo esperado 🙂 Antes y después de entrar en la ejecución de cada método pasa por nuestro TraceInterceptor y deja una traza de lo que ha ocurrido.

Esto que hemos visto es una configuración básica, se podrían hacer muchas más cosas. Por ejemplo, si cambiamos en nuestro contenedor lo que teníamos por lo siguiente, nos proporcionaría el mismo resultado:

1
2
3
4
5
6
7
8
9
    public class Composer: NinjectModule
    {
        public override void Load()
        {
           this.Kernel.Intercept(c => true).With<TraceInterceptor>();
           this.Bind<IDummyService>().To<DummyService>(); //.Intercept().With<TraceInterceptor>();
        }
 
    }

En el código anterior le estamos indicando que intercepte todo nuestro Kernel.
Otras cosas que se podrían hacer es interceptar métodos o clases con determinado atributo, o vía reflection, añadir los interceptors en función de cierto tipo de cosas, etc.

En resumen, es una herramienta muy potente para cosas como añadir caching, manejo de excepciones, loggin… pero hay que tener mucho cuidado, porque podemos introducir demasiada magia y acabar perdiendo el hilo de lo que estamos haciendo. En cierto tipo de aplicaciones también deberíamos tener en cuenta que esto introduce tiempo adicional, pero para la mayoría de los mortales esto no es relevante 🙂
¡Un saludo!
PD: Este enlace es muy interesante y tiene varios ejemplos.

One thought on “Ninject. Configurar interceptors”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *