Implementando IoC en ASP.NET MVC 2 con Castle Windsor

En un post anterior hablamos sobre los beneficios de aplicar el patrón IoC en nuestros proyectos y vimos un breve ejemplo utilizando Unity de la gente de Patterns & Practices. Además comentamos que existen diferentes frameworks que nos facilitan estas tareas, uno de ellos es Castle Windsor y es el que vamos a utilizar en este caso.

Castle Project

Castle Project

En este post vamos a explicar cómo configurar IoC utilizando este framework en una aplicación web ASP.NET MVC 2.

El primer paso será descargar desde la página oficial del proyecto Castle las librerías Windsor 2.1link de la descarga.

El siguiente paso será crear una aplicación ASP.NET MVC 2 y agregar las siguientes referencias a nuestro proyecto:  Castle.Core.dll, Castle.MicroKernel.dll, Castle.Windsor.dll.

Referencias

Referencias

Para seguir con el ejemplo del post inicial, vamos a crear una interfaz denominada IRedSocialService y dos servicios que la implementen (por ejemplo TwitterService y FacebookService). La definición de la interfaz podría ser la siguiente:

public interface IRedSocialService
{
    IEnumerable ObtenerContactos();
}

La implementación de ambos servicios se las dejo a ustedes 🙂 .

A continuación vamos a configurar nuestras dependencias utilizando Windsor Container. Para ello lo que vamos hacer es realizar algunas modificaciones en nuestro archivo de configuración Web.config y separar las configuraciones propias del contenedor en otro archivo de configuración aparte que vamos a crear y llamar Castle.config.

La estructura del proyecto sería más o menos la siguiente:

Estructura proyecto

Estructura proyecto

Al archivo web.config vamos a agregarle lo siguiente:

<configuration>
<configSections>
...
<section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
</configSections>

<castle configSource="Castle.config"></castle>
 …
</configuration>

Qué estuvimos haciendo? En primer lugar indicando que la sección castle será utilizada por CastleSectionHandler y que es encuentra en el assemblie Castle.Windsor. En segundo lugar estamos indicando que la sección castle se encuentra dentro del archivo castle.config y que vamos a configurar a continuación.

Al archivo castle.config vamos a agregar lo siguiente:

<castle>
<components>
<component id="ServicioRedSocial"
                    type="Castle.Windsor.Logica.TwitterService, Castle.Windsor.Logica"
                    service="Castle.Windsor.Logica.IRedSocialService, Castle.Windsor.Logica" />
</components>
</castle>

Qué estuvimos haciendo? Estuvimos registrando nuestro primer elemento del contenedor. Vamos a explicar cada tag por parte:

  • id: nombre con el que queremos identificar ese elemento en particular.
  • service: la interfaz sobre la cual vamos hacer las inyecciones.
  • type: el tipo concreto que queremos que el contenedor devuelva como implementación de la interfaz definida.

Teniendo en cuenta la configuración actual, cuando solicitemos una instancia de IRedSocialService deberíamos obtener un objeto TwitterService. Pero ojo, aún falta para que esto suceda…

Ahora es turno de trabajar con nuestro controlador que llamaremos RedSocialController, y en donde vamos a definir nuestro contenedor y nuestro servicio de la siguiente manera:

private WindsorContainer contenedor;
private IRedSocialService servicio;

Ahora vamos a trabajar sobre el constructor del controlador y la acción Index :

public class RedSocialController : Controller
{
    private WindsorContainer contenedor;
    private IRedSocialService servicio;

    public RedSocialController()
    {
        this.contenedor = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
        this.servicio = contenedor.Resolve<IRedSocialService>();
    }

    public ActionResult Index()
    {
        IEnumerable contactos = servicio.ObtenerContactos();
        return View(contactos);
    }
}

Dentro del constructor del controlador creamos nuestro contenedor, indicándole que la sección castle del archivo de configuración tiene las configuraciones correspondientes. Luego seteamos nuestro servicio, pidiendo al contenedor que nos devuelva la instancia que hayamos configurado. Finalmente vamos a tener disponible nuestro servicio en cada una de las acciones de RedSocialController.

Ejecutamos y listo!

Ejemplo IoC

Ejemplo IoC

Si ahora deseamos recuperar los contactos de otra red social, solo deberemos modificar nuestro archivo de configuración y todo solucionado!

Pero porqué quedarse acá? Porqué no aprovechar algunas de las características de ASP.NET MVC y automatizar aún más nuestras inyecciones?… Comencemos entonces!

La idea ahora es hacer las inyecciones sobre los parámetros del constructor de los controladores, y evitar tener que crear en cada controlador el contenedor y luego setear los servicios. Pero cómo podemos hacer esto? …  ASP.Net MVC al crear un controlador utiliza una factory, una instancia de la clase DefaultControllerFactory que requiere que los controladores tengan el constructor por defecto. Cómo esto no nos sirve, ya que la idea es tener constructores con parámetros, podemos crear nuestra propia factory y luego especificarle a MVC que las utilice (tengamos en cuenta que deberá heredar de DefaultControllerFactory).

Entonces vamos a crear nuestra propia factory que se va a encargar de realizar las inyecciones de dependencias sobre los parámetros de nuestros controladores (y por nosotros :)). Para esto vamos a crear una clase denominada WindsorControllerFactory.

Veamos cómo queda nuestra factory:

public class WindsorControllerFactory : DefaultControllerFactory
{
    WindsorContainer container;

    public WindsorControllerFactory()
    {
        container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
        var controllerTypes = from t in Assembly.GetExecutingAssembly().GetTypes()
                                     where typeof(IController).IsAssignableFrom(t)
                                     select t;

        foreach (Type t in controllerTypes)
            container.AddComponentLifeStyle(t.FullName, t,LifestyleType.Transient);
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
            return null;

        return (IController)container.Resolve(controllerType);
    }
}

Como vemos debemos sobre-escribir el método GetControllerInstance e inyectar las dependencias al momento de devolver el controlador – esto en tiempo de ejecución.

Finalmente debemos indicarle a MVC que utilice nuestra controller factory, para eso agregamos en nuestro archivo global.asax la siguiente línea dentro del método Application_Start():

ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory());

Finalmente volvemos a redefinir nuestro controlador de la siguiente forma:

public class RedSocialController : Controller
{
    private IRedSocialService servicio;

    public RedSocialController(IRedSocialService servicio)
    {
        this.servicio = servicio;
    }

    public ActionResult Index()
    {
        IEnumerable contactos = servicio.ObtenerContactos();
        return View(contactos);
    }
}

A diferencia del controlador anterior, este tiene definido como parámetros del constructor el tipo de interfaz que vamos a utilizar en el servicio, la cuál es inyectada por nuestra factory al momento de inicializarse el controller. También podemos ver que no es necesario crear el contenedor y setear nuestro servicio, lindo no!?

Anuncios