Inyección de Dependencias automática con proyecto MVC de ejemplo

Cuando comenzamos un nuevo proyecto hay algunas tareas que no están directamente relacionadas con el objetivo mismo de la aplicación, sino que tienen que ver más con la arquitectura. Si queremos código testeable, fácil de mantener y elegante, necesitaremos implementar #IoC (Inversión de Control) y #DI (Inyección de Dependencias).

He creado una pequeña libreria “multi-motor-ioc” que se encargará de registrar los interfaces o servicios por tí, y cualquier clase adicional que necesites con la ayuda de atributos de clase. Soporta de arranque tres de los contenedores IoC más utilizados: Autofac, Unity y nInject, además de permitir implementar cualquier otro que uses o necesites en tu proyecto.

Descarga el código desde mi repositorio en BitBucket con GIT: http://repo.joanvilarino.info/diautoregistrationformvc
O descarga directamente el proyecto en formato ZIP: http://repo.joanvilarino.info/diautoregistrationformvc/downloads

La primera cosa que debes tener en cuenta es que he creado esta librería como ejemplo para una serie de futuros artículos, y que en este momento solamente soporta un pequeño conjunto de funciones, que iré ampliando en el futuro con los siguientes artículos de la serie. Si necesitas algo que no está todavía soportado, eres totalmente libre de implementarlo tú mismo (un pull request sería tremendo) o esperar al “próximo episodio”.

Dicho esto, comenzamos nuestro super impresionante proyecto, y tenemos que enfrentarnos a la inyección de dependencia…

La mayoría de contenedores IoC disponibles tienen extensiones para manejar los proyectos MVC, así que no tienes que registrar ni resolver las clases controlador, pero aún así tendrás que registrar manualmente todas las dependencias de interfaces que esos controladores tengan en su constructor (los servicios que van a utilizar para su funcionamiento).

Cuando trabajas en un proyecto pequeño, eso no es un problema. Siempre puedes añadir un pequeño método en tu clase Startup.cs, y registrar tus interfaces manualmente de esta manera (dependiendo del contenedor IoC que uses, en este ejemplo nInject).


// Inicializar contenedor
var _kernel = new StandardKernel();
// Registar tipo con interfaz
_kernel.Bind<IMiInterfaz>().To<MiClase>();

Pero ahora imagínate que tienes que hacer un gran proyecto con cientos de interfaces o miles de clases. El métoco podría crecer hasta unas dimensiones inmantenibles.

La pequeña libreria que os propongo, DependencyInjectionServices, registrará los interfaces por tí, permitiendo manetener un pequeño método que hará “toda la magia”.

Para éste ejemplo, he usado una plantilla MVC5 de Visual Studio 2013 Community Edition (gratuito), así que los packages utilizados son para la version MVC5, incluyendo su IDependencyResolver para que MVC resuelta sus controladores, pero debería ser fácil cambiar a cualquier otra versión, o otro tipo de proyecto no Web o MVC.

Usando DependencyInjectonService, todo el proceso de registro debería ser algo tan sencillo como esto:

private void ConfigureSimpleDependencyInjector(IDependencyInjectionEngine engine)
{
    new DependencyInjectionService(engine)
        .RegisterDependencies(new DependencyInjectionSetting()
        {
            AssemblyFilter = new List<string> { "DITest.", "DIRegistrationTest" }
        });
}

Puedes ver este método (un poco diferente pero casi lo mismo) en la clase Startup.cs del proyecto MVC de ejemplo (te lo has bajado, ¿verdad?)

Entrando en detalle, puedes ver que crea una instancia de nuestro DependencyInjectionService pasándole el motor IoC engine que hayamos elegido, y entonces llamando al método RegisterDependencies con un nuevo objeto de la clase DependencyInjectionSetting que establecerá las opciones para el registro.

Las opciones que tenemos disponibles para configurar el auto-registro en la clase DependencyInjectionSetting, por el momento, son las siguientes:

AssemblyFilter (Requerida)
Esto es una lista de strings con los nombres exactos o parciales de los ensamblados que queremos incluir en el rastreo de tipos. Una vez la librería ha conseguido esos ensamblados, buscará en ellos los tipos que han sido “decorados” con el atributo DependencyInjectionRegisterAttribute, y los registrará en el contenedor IoC para su futura resolución.

ExternalAssemblyFilter (Opcional)
Esta propiedad actua como la anterior, pero esta vez buscara archivos de ensamblado, permitiéndonos registrar tipos que se encuentran en ensamblados no referenciados en nuestro proyecto, que serán implementados de forma dinámica. No olvidéis incluir la extensión .dll o .exe (aquí buscamos archivos…). Estos ensamblados externos, por el momento, deberán estar en la carpeta /bin de nuestro proyecto.

Si no se especifica, no se añadirán ensamblados externos.

ClassesForcedToRegister (Optional)
Añadiendo tipos a esta lista, forzaremos a la libreria a registarlos. Por ejemplo, si tu motor elegido no puede hacer auto-registro de controladores MVC, necesitarás añadirlos a esta lista de esta manera:

ClassesForcedToRegister = Assembly.GetExecutingAssembly().GetTypes()
                        .Where(t => typeof (Controller).IsAssignableFrom(t))
                        .ToList()

Seleccionando qué tipos se registarán
Como he dicho antes, la libreria encontrará las clases a registrar por su cuenta. Para conseguir esto, implementa dos clases Attribute que se usaran para Añadir o Ignorar un tipo determinado, incluyéndolas en la declaración del tipo.

Incluyendo tipos con DependencyInjectionRegister
Si quiere incluir un tipo en el registro, añade este atributo a la declaración del mismo:

    [DependencyInjectionRegister]
    public class MiServicio : IMiServicio
    {
         // Métodcos de la clase...
    }

Si tu IoC no lo hace automáticamente, podrías usar este atributo en todos tus controladores para que fuesen registrados automáticamente.

Excluyendo tipos con DependencyInjectionIgnore
Si añades este atributo a cualquier tipo, la libreria lo ignorará y no será auto-registrado.

Esto es útil en las siguientes situaciones:

  • Cuando se añade una clase al contenedor IoC, se comprueban los interfaces que implementa, y cada unos de esos interfaces son emparejados con esta clase. Si no quieres que alguno de ellos sea registrado, añade el atributo en la declaración del interfaz para que sea ignorado.
  • Cuando se implementa el mismo interfaz en más de una clase, deberías usar el atributo de manera que ignore las clases que no quieres que se emparejen con él. Si la librería encuentra más de una clase asociada al mismo interfaz, se producirá un error.
  • Cuando quieres hacer registro personalizado de alguna clase (cambiar el ciclo de vida del objeto o cualquier otra función no soportada todavía por la librería), puedes añadir este atributo para ignorar la clase y registrarla tú manualmente.

    Creando tus controladores
    Ahora que tu aplicación está lista para auto-registrar cualquier cosa que le tires, comienza el desarrollo del proyecto en sí, y como dice el “manual del buen programador”, la mejor manera es inyectar todos los servicios en el constructor del controlador, de manera que sean resueltos automáticamente por el contenedor IoC. Aquí va el ejemplo:

        public class HomeController : Controller
        {
            private IExternalService _myService;
            private ILocalService _anotherService;
    
            public HomeController(IExternalService myService, ILocalService anotherService)
            {
                // Guardamos los servicios inyectados para usarlos después...
                _myService = myService;
                _anotherService = anotherService;
            }
    
            public ActionResult Index()
            {
                _myService.TerrificMethodOne();
                ViewBag.myServiceResult = _myService.SayHello();
                ViewBag.anotherServiceResult = _anotherService.GuessWhat();
                return View();
            }
        }
    

    Eso es todo!! No hace falta nada más. Si has marcado todos tus servicios con DependencyInjectionAttribute, deberían implementarse recursivamente, y no deberías hacer ni un solo new en tu aplicación!

    Muy bien, ya se como se usa, pero … como está hecho?
    En los siguientes artículos intentaré explicar algunas de las técnicas usadas para crear esta librería (como usar atributos personalizados, uso de reflection para encontrar interfaces, clases y herencias, como crear e inyectar los “motores” para la librería, etc…). Simplemente mantente informado – como sé que ya haces – en mi blog, y todo será revelado!

    Gracias!
    Realmente agradezco si has llegado hasta tan lejos y aún más si te ha gustado y / o planeas usarlo en un futuro.

    Visita mi repositorio en BitBucket si quieres ver más proyectos de ejemplo!!

    Follow me

    Joan Vilariño

    Senior .NET Developer at Ohpen
    Follow me

3 comentarios

  1. Muy buen artículo y gran blog!!!!

  2. Joan Vilariño

    17/01/2015 a las 18:49

    Gracias!

  3. Muy buen artículo!
    Un saludo.

Deja un comentario

Tu dirección de correo electrónico no será publicada.

*

A %d blogueros les gusta esto:
Ir a la barra de herramientas