Desacoplando controladores ASP.NET MVC, paso a paso

ASPNETMVCHace poco, en el post donde trataba la inyección de dependencias y desacoplamiento de Hubs de SignalR, el amigo Maxxx comentaba que podría estar bien ver cómo podríamos emplear las mismas técnicas con ASP.NET MVC.

Y ciertamente, me ha parecido muy interesante porque es un escenario que encuentro habitualmente en empresas de desarrollo: comprenden los beneficios de reducir el acoplamiento entre componentes, pero les parece algo demasiado complejo como para aplicar en su día a día porque desconocen cuáles son y cómo usar las herramientas de que disponemos para conseguirlo.

Por tanto, en este post vamos a ver, paso a paso y de forma totalmente práctica, cómo evolucionar desde un controlador MVC fuertemente acoplado a clases del modelo hasta otro totalmente desacoplado usando inyección de dependencias y contenedores de inversión de control.

Y aunque las técnicas y ejemplos que mostraremos están muy enfocados a controladores MVC, los conceptos tratados son aplicables en cualquier tipo de tecnología y arquitectura.

0. Punto de partida

Vamos a partir de la siguiente clase del modelo, de la que sólo mostramos las partes importantes:

1
2
3
4
5
6
public class MyAppServices: IDisposable
{
    public IEnumerable<Product> GetAllProducts() { ... }
    public IEnumerable<Product> GetProductsByCategory(int categoryId) { ... }
    public void Dispose() { ... }
}

Y vemos ahora el siguiente controlador MVC, funcionalmente correcto, que utiliza la clase anterior para obtener datos con los que poblar las vistas. Como curiosidad, deciros que el código está basado en hechos reales:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ProductsController : Controller
{
    public ActionResult Index()
    {
        using (var services = new MyAppServices())
        {
            var data = services.GetAllProducts();
            return View(data);
        }
    }
    public ActionResult ByCategory(int categoryId)
    {
        using (var services = new MyAppServices())
        {
            var data = services.GetProductsByCategory(categoryId);
            return View(data);
        }
    }
}

Observad que en este controlador existe un acoplamiento total con la clase MyAppServices, que es instanciada en cada una de las acciones que hemos implementado. Mucho código repetido, una dependencia total respecto a la implementación de la clase MyAppServices, y la imposibilidad de realizar pruebas unitarias a los métodos.

A continuación, vamos a ir haciendo evolucionar este controlador hasta que alcancemos el punto donde queremos llegar: el desacoplamiento total, manteniendo intactas las funcionalidades proporcionadas.

1. Extraer código de creación y liberación de dependencias

Uno de los aspectos negativos que hemos notado en el controlador de partida es que el código de sus acciones es demasiado extenso, teniendo en cuenta que sólo deben retornar una vista con información obtenida desde el modelo. Esto rompe con el principio básico DRY (Don’t Repeat Yourself).

La utilización del bloque using es muy correcta, es importante asegurarse de que los recursos son liberados al finalizar el tratamiento de la petición. Sin embargo, la instanciación de la clase del modelo la estamos replicando en demasiados puntos… ¿Qué ocurriría si en el futuro no queremos que sea la clase MyAppServices, sino cualquier otra, la usada por las acciones? ¿O si hacemos que el constructor de dicha clase reciba un parámetro? Pues tendríamos que tocar demasiado código. Serían cambios con un impacto muy alto en todos los componentes cohesionados con esta clase.

Para minimizar este riesgo, una primera medida podría ser la refactorización del código, llevándonos las tareas comunes de instanciación y liberación de la clase del modelo a un lugar que pueda ser compartido por las acciones. El resultado podría ser el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ProductsController : Controller
{
    private MyAppServices _services;
    public ProductsController()
    {
        _services = new MyAppServices();
    }
 
    public ActionResult Index()
    {
        var data = _services.GetAllProducts();
        return View(data);
    }
 
    public ActionResult Category(int categoryId)
    {
        var data = _services.GetProductsByCategory(categoryId);
        return View(data);
    }
 
    protected override void Dispose(bool disposing)
    {
        if (disposing)
            _services.Dispose();
    }
}

Como podemos ver, los métodos de acción se han simplificado notablemente, eliminando todo el “ruido” que introducía el bloque using y la instanciación de la clase del modelo. Ahora estas acciones son más fáciles de leer y, por tanto, de modificar y mantener.

Siguiendo con el código anterior, la creación de la instancia nos la hemos llevado al constructor, por lo que si quisiéramos sustituir la clase del modelo por otra ya sólo tendríamos que tocar una línea de código en el controlador. Al llamar en un único punto el new, hemos eliminado casi completamente el pegamento que unía el componente instanciado con el instanciador.

Por último, seguimos manteniendo el control sobre el tiempo de vida de MyAppServices. Se instancia en el constructor del controlador, y se libera justo al acabar el proceso de la petición, aprovechando que podemos sobrescribir el método Dispose().

Vamos mejorando, pero aún hay mucho por hacer 🙂

2. Abstracción de las clases del modelo

Aunque la refactorización anterior nos ha llevado a un código más limpio, aún seguimos teniendo un controlador muy acoplado a la clase MyAppServices. El próximo paso será romper esta cohesión abstrayéndonos mediante interfaces.

La idea es eliminar de nuestro controlador la referencia a la clase concreta del modelo que estamos usando. Para ello, realizaremos dos operaciones:

  • En primer lugar, extraeremos un interfaz con los miembros que nos interesen desde la clase MyAppServices, a la que llamaremos IAppServices. Y por supuesto, reflejamos en la clase del modelo que implementa ese interfaz, de forma que el asunto quedaría algo así:    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface IAppServices: IDisposable
    {
        IEnumerable<Product> GetAllProducts();
        IEnumerable<Product> GetProductsByCategory(int categoryId);
    }
    public class MyAppServices: IAppServices
    {
        // ...
    }
  • En segundo lugar, modificamos el controlador, de forma que el miembro _services deje de ser del tipo MyAppServices y pase a usar el nuevo interfaz:    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class ProductsController : Controller
    {
        private IAppServices _services;
     
        public ProductsController()
        {
            _services = new MyAppServices();
        }
     
        // ... Action methods and Dispose
    }

Aunque pueda parecer lo contrario, lo que hemos hecho es realmente importante. Ahora, salvo en el constructor, no queda en nuestra clase controlador ninguna referencia a la clase concreta del modelo que estamos utilizando. De cara a la implementación de las acciones, nos da igual qué objeto se encuentre almacenado en _services, lo único que nos interesa es que cumpla el contrato definido por la interfaz IAppServices; el uso de esta abstracción es un paso más hacia el desacoplamiento total.

Veamos ahora cómo resolver ese “salvo en el constructor”…

3. Inyección de Dependencias

En este momento tenemos ya un código de controlador bastante limpio y desacoplado de la clase del modelo, pero aún seguimos teniendo un poco de “pegamento” en el constructor.

Si quisiéramos sustituir la implementación MyAppServices por otra, aún tendríamos que modificar todos los controladores que usaran esta técnica.

Tampoco seríamos capaces aún de realizar pruebas unitarias a las acciones, puesto que éstas implicarían la ejecución de código de MyAppServices. Y sabemos que esto no serían pruebas unitarias, sino de integración, ¿verdad?

Bien, ¿y cómo podríamos solucionar esto? Pues mediante la inyección de dependencias. Este principio de diseño sugiere la eliminación de dependencias rígidas de un componente (como la provocada por el uso del new en el constructor) haciendo que sea otro componente el que suministre al primero todos aquellos objetos que necesite para funcionar.

En otras palabras, si desde ProductsController necesitamos para funcionar una instancia de cualquier tipo que implemente las operaciones definidas en el interfaz IAppServices, simplemente debemos obligar a que alguien nos la suministre.

La implementación práctica de este principio en nuestro ejemplo se podría materializar de la siguiente forma, modificando ligeramente el constructor:

1
2
3
4
5
6
7
8
9
10
11
public class ProductsController : Controller
{
    private IAppServices _services;
 
    public ProductsController(IAppServices services)
    {
        _services = services;
    }
 
    // Action methods and Dispose
}

Observad que ahora el constructor de la clase exige que alguien le suministre las dependencias, u objetos que necesita para funcionar correctamente; no se podrá instanciar el controlador de otra forma. Y dado que tenemos ya definido el interfaz, no es necesario indicar un tipo concreto, simplemente el contrato que define las operaciones que usaremos desde el controlador, sea cual sea la clase que las implemente.

En estos momentos ya hemos eliminado por completo el acoplamiento entre ProductsController y MyAppServices: de hecho, no queda ninguna referencias hacia la clase concreta del modelo que usamos en el controlador.

Otra ventaja de haber dado este paso es que nos permite ya realizar pruebas unitarias completas sobre nuestros métodos de acción. Desde el código de test podríamos instanciar la clase ProductsController, suministrarle una referencia hacia un objeto falso (que implemente el interfaz requerido) y comprobar el funcionamiento de las acciones, sin salir de su ámbito.

Ah, antes de continuar, permitidme un consejo que suelo dar a los equipos de desarrollo con los que trabajo:

Programa siempre tus componentes como si fueras a hacerles pruebas unitarias.
Aunque no las hagas.

Es decir, el hecho de no realizar pruebas unitarias no debe valer como excusa para no aplicar principios y buenas prácticas, como el desacoplamiento o la inyección de dependencias, cuyo uso sólo traerá beneficios a vuestras aplicaciones.

Por recapitular un poco, el código del controlador lo tendríamos en este momento de la siguiente forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ProductsController : Controller
{
    private IAppServices _services;
 
    public ProductsController(IAppServices services)
    {
        _services = services;
    }
 
    public ActionResult Index()
    {
        var data = _services.GetAllProducts();
        return View(data);
    }
 
    public ActionResult Category(int categoryId)
    {
        var data = _services.GetProductsByCategory(categoryId);
        return View(data);
    }
 
    protected override void Dispose(bool disposing)
    {
        if (disposing)
            _services.Dispose();
    }
}

Sin embargo, si en estos momentos ejecutamos nuestra aplicación, veremos que no funciona: ASP.NET MVC es incapaz de crear un controlador ProductsController, puesto que no dispone de un constructor sin parámetros. Es totalmente lógico: el framework no puede llamar al constructor porque no sabe qué parámetro enviarle.

4. Suministrando dependencias al controlador con un Dependency Resolver

Ojo, que lo que vamos a ver en este punto es sólo por “cultura general”, para conocer los mecanismos que hay por detrás de ASP.NET MVC, pero no es estrictamente necesario. Las técnicas mostradas aquí son ampliamente superadas por las que utilizaremos en el siguiente epígrafe.

Bueno, antes hemos comentado que para instanciar un controlador del tipo ProductsController, “alguien” debe suministrarle las dependencias, es decir, llamar a su constructor pasándole como parámetro la instancia de la clase del modelo a utilizar. 

La instanciación del controlador es un proceso que se lleva a cabo desde el interior del framework, aunque existen varias fórmulas que permiten intervenir en este proceso. Una posibilidad es usar el Dependency Resolver, de forma similar a como describimos hablando de SignalR.

El Dependency Resolver es un mecanismo incluido en ASP.NET MVC que actúa como suministrador de instancias centralizado. Cuando el framework necesita crear un objeto de cualquier tipo, en primer lugar solicita la nueva instancia al Dependency Resolver, y sólo si éste no se la facilita, lo crea de forma manual.

Así, cuando ASP.NET MVC va a instanciar el controlador ProductsController, primero pregunta al Dependency Resolver si le puede proporcionar una instancia. Si tomamos el control en este punto, podríamos automatizar la inyección de dependencias en los controladores.

Para ello, debemos crear una clase que implemente el interfaz IDependencyResolver, e informar al framework que se trata del nuevo componente encargado de la resolución. El código simplificado de esta clase podría ser el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyDependencyResolver: IDependencyResolver
{
    public object GetService(Type serviceType)
    {
        if (serviceType == typeof (ProductsController))
            return new ProductsController(new MyAppServices());
        return null;
    }
 
    public IEnumerable<object> GetServices(Type serviceType)
    {
        return Enumerable.Empty<object>();
    }
}

Como seguro podéis intuir, el método GetService() será el llamado por ASP.NET MVC en el momento de instanciar el controlador. Por otra parte, es necesario registrar en el framework este componente, lo cual podemos conseguirlo mediante el siguiente código, ejecutado durante la inicialización de la aplicación (en el método Application_Start() del global.asax puede ser un buen lugar para hacerlo):

1
DependencyResolver.SetResolver(new MyDependencyResolver());

De esta forma, al ejecutar nuestra aplicación, veríamos que todo funciona de nuevo. El framework está llamando al nuevo Dependency Resolver, y nuestro controlador está siendo creado junto con las dependencias que necesita satisfacer, en este caso el objeto que implemente IAppServices.

Además, en el Dispose() del controlador seguimos manteniendo la liberación de recursos de nuestra clase del modelo, por lo que todo seguirá funcionando correctamente.

Pero esto es demasiado trabajo, ¿no? Pensad que podemos tener controladores con dependencias hacia varios objetos, y que incluso éstos podrían tener, a su vez, dependencias con otros objetos, por lo que la instanciación manual del controlador podría ser bastante tediosa. También deberíamos introducir en los controladores la llamada a la liberación de recursos de las dependencias, y es bastante fácil que se nos olviden algunos…

5. Usar Contenedores de Inversión de Control

La solución definitiva a nuestros problemas pasa por el uso de los llamados contenedores de inversión de control (IoC Containers), que, entre otras cosas, son componentes especializados en la creación de instancias de objetos, así como en la gestión del ciclo de vida de éstas. En cierto sentido son similares a los dependency resolvers que hemos comentado anteriormente, pero van más allá y nos lo pondrán todo bastante más fácil.

Muy resumidamente, un contenedor IoC mantiene un registro de asociaciones entre interfaces y clases concretas. Cuando un componente externo solicita al contenedor una instancia de un tipo determinado, éste utiliza su registro para satisfacer las dependencias que sean requeridas en cada momento.

Así, si alguien solicita al container una instancia de la clase “X” cuyo constructor requiere como parámetro una instancia del interfaz “I”, el contenedor buscará en su registro la clase asociada a dicho interfaz y obtendrá automáticamente una instancia de ésta. Una vez la tenga, instanciará la clase “X” pasándole como parámetro a su constructor el objeto creando anteriormente.

Existen gran cantidad de contenedores de inversión de control (Ninject, Unity, Automapper, Autofac, Windsor, etc.), y la elección de uno u otro es muchas veces sólo cuestión de preferencias personales. Además, dado que se trata de componentes estándar y válidos para casi cualquier tipo de proyecto, se pueden encontrar extensiones que facilitan su integración en ASP.NET MVC, por lo que es conveniente siempre elegir uno que ya esté preparado para este framework.

Todos los contenedores son conceptualmente iguales; para un uso básico simplemente notaremos que hay entre ellos diferencia de sintaxis, por lo que vamos a centrarnos en uno de los más populares: Unity.

Unity (http://unity.codeplex.com/) es un potente contenedor promovido por  la iniciativa de Patterns & Practices de Microsoft. Como otros, dispone de extensiones específicas para ASP.NET MVC y se puede instalar muy fácilmente a través de Nuget usando el package “Unity.Mvc3” (ojo, que a pesar de su desafortunado nombre es válido también para la versión 4 del framework):

PM> Install-Package unity.mvc3

Una vez instalado, el siguiente paso es registrar en el contenedor las asociaciones entre interfaces y clases concreta en el archivo bootstrapper.cs que la instalación habrá dejado en el raíz de nuestro proyecto. Como la ubicación por defecto de este archivo es terrible, os recomiendo moverla a App_Start, puesto que el registro de clases se debe ejecutar durante el arranque de la aplicación y así seguimos las convenciones de ubicación de archivos.

En nuestro caso, lo único que tenemos que indicar es que cuando alguien solicite al contenedor una instancia para el interfaz IMyServices, se debe obtener y retornar un objeto de la clase concreta MyAppServices. Además, vamos a indicar que su tiempo de vida se limitará a la petición actual, es decir, que debe ser liberada cuando la petición haya sido procesada; el framework detectará que implementa IDisposable e invocará su método Dispose() de forma automática.

Todo esto se consigue en Unity introduciendo en bootstrapper.cs la siguiente línea:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// File: bootstrapper.cs:
public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();
        DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    }
 
    private static IUnityContainer BuildUnityContainer()
    {
        var container = new UnityContainer();
        container.RegisterType<IAppServices, MyAppServices>(
            new HierarchicalLifetimeManager()
        );
        return container;
    }
}

Nota: si sois de los que preferís remangaros y hacer las cosas por vosotros mismos, os recomiendo que leáis este brutal post de Eduard donde trata sobre la inyección de dependencias y cómo conseguir que las instancias tengan un tiempo de vida per-request. Muy aconsejable.

Volviendo al código, observad que si en el futuro queremos sustituir en la aplicación la implementación de MyAppServices por otra cualquiera que implemente el interfaz IAppServices, sólo tendríamos que modificar el registro en el contenedor IoC. El controlador no se vería afectado de ninguna forma.

En este mismo punto incluiríamos todas las dependencias necesarias para que puedan construirse los objetos. Si, por ejemplo, MyAppServices depende a su vez de una clase que implementa IDataContext, debemos registrar también una asociación para dicho interfaz, de forma que el contenedor tenga toda la información necesaria para satisfacer las dependencias necesarias y construir el grafo de objetos completo.

En cualquier caso, una vez registradas todas las asociaciones en el método BuildUnityContainer(), sólo nos faltaría introducir una llamada a Bootstrapper.Initialize() en el método Application_Start() del global.asax, de forma que se ejecute durante la inicialización del sistema.

Además, dado que hemos indicado al contenedor que la instancia creada debe ser liberada al finalizar la petición, su método Dispose() será ejecutado automáticamente, por lo que ya podemos eliminar del controlador la implementación de Dispose(), desde donde antes realizábamos esta operación de forma manual.

Y hechos estos dos últimos ajustes, podemos decir que hemos terminado 🙂

6. Resumiendo

Como podemos ver a continuación, hemos conseguido que nuestro controlador quede bastante limpio y testeable, y sus acciones muy concisas y centradas en sus objetivos funcionales. Además, su constructor deja clarísimas las dependencias del mismo, y se mantiene un total desacoplado de las clases del modelo utilizadas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ProductsController : Controller
{
    private IAppServices _services;
 
    public ProductsController(IAppServices services)
    {
        _services = services;
    }
 
    public ActionResult Index()
    {
        var data = _services.GetAllProducts();
        return View(data);
    }
 
    public ActionResult Category(int categoryId)
    {
        var data = _services.GetProductsByCategory(categoryId);
        return View(data);
    }
}

Hemos eliminado también el código de liberación de dependencias, y hemos delegado al contenedor la instanciación, a veces compleja, de todos los componentes usados.

Aunque al hacerlo paso a paso el post ha quedado un poco extenso y podría llegar a parecer complejo, nada más lejos de la realidad. Lo único que tenemos que hacer y tener en cuenta para conseguir este tipo de controladores es, a modo de resumen:

  • Detectar cuáles son los objetos de los que depende nuestro controlador, es decir, determinar las dependencias que vamos a eliminar.
  • Preparar el constructor para recibir las dependencias, siempre en forma de interfaces, y almacenarlas en miembros privados.
  • Desde las acciones, usar los componentes externos a través los miembros privados en los que los hemos almacenado en el constructor.
  • Instalar un contenedor IoC y registrar en él las asociaciones entre las interfaces y clases, con objeto de que los controladores puedan ser instanciados automáticamente, siempre teniendo en cuenta la gestión del tiempo de vida de los objetos.
  • Si las dependencias así lo requieren, implementar en ellas el interfaz IDisposable, de forma que el container se encargue de liberar sus recursos al finalizar el proceso de la petición.

Podéis descargar el proyecto que hemos implementado en este post desde mi Skydrive.

Espero que os sea útil, y los que todavía no habéis dado el salto a este tipo de diseño os animéis a probarlo. Os aseguro que no os vais a arrepentir 🙂

Publicado en Variable not found.

4 comentarios sobre “Desacoplando controladores ASP.NET MVC, paso a paso”

  1. Y si usas el patron decorador para montarte una cadena de responsabilidades la cosa ya esta maqueadita del todo (a no ser que te pongas con AOP).

    interface IBankingModule
    {
    void PerformTransfer(int amount, int OriginAccotunID, int DestinationAccountID);
    }

    public class BankingModuleLogger : IBankingModule
    {
    private IBankingModule _innerModule;
    private ItraceModule _traceModule;

    //inject IBankModule to decorate it and a trace provider. (trace in BD, in text file, etc)
    public BankingModuleLogger(IBankModule innerModule, ItraceModule traceModule)
    {
    this._innerModule = innerModule;
    this._traceModule = traceModule;
    }

    public void PerformTransfer(int amount, int originAccotunID, int destinationAccountID)
    {
    this._traceModule.write(String.Format(«Performing money transfer from account {0}to account {1}», originAccotunID, destinationAccountID));
    this._innerModule.PerformTransfer(amount, originAccotunID, destinationAccountID);
    }
    }

    public class BankingModuleSecurity : IBankingModule
    {
    private IBankingModule _innerModule;
    private ISecurityModule _securityModule;

    //inject IBankModule to decorate it and a security provider. (roles/actionAllowed in BD, XML , App.config, etc).
    //Security provider has a securityContext injected. securityContext must be pseudoSingleton (app escritorio) or in web session (web app). This behaviour can be configured in Unity.
    public BankingModuleSecurity(IBankModule innerModule, ISecurityModule securityModule)
    {
    this._innerModule = innerModule;
    this._securityModule = securityModule;
    }

    public void PerformTransfer(int amount, int OriginAccotunID, int DestinationAccountID)
    {
    if (!this._securityModule.hasPermissions(SecureActions.TransferMoney))
    {
    throw new SecurityException(String.Format(«No permissions for user: {0} whit roles: {1} for action {2}», _securityModule.securityContex.User, _securityModule.securityContex.Roles.ToString(), SecureActions.TransferMoney.ToString()));
    }
    this._innerModule.PerformTransfer(amount, OriginAccotunID, DestinationAccountID);
    }
    }

    public class BankingModule : IBankingModule
    {
    public void PerformTransfer(int amount, int OriginAccotunID, int DestinationAccountID)
    { //bussines code }
    }

  2. Como ya postee en el post de Eudardo que comentas, para MVC, recomiendo Autofac en vez de Unity, más que nada porque permite de forma sencilla definir el ciclo de vida de los objetos como un request (cada request tiene sus propios objetos). Con eso por ejemplo, podemos guardar el «UserController» una vez authenticado para toda la request sin temor a que se mezclen entre usuarios.

  3. Hola!

    @juan, hombre, más que una dependencia yo diría una referencia 😉 No hay nada en el controlador (ni tiene que haberlo en otra parte) acoplado a Unity; el IoC debe ser un componente ortogonal al resto.

    @crowley, efectivamente, currándotelo un poco a nivel de registro puedes crear decoradores como esos, gracias por el detallado ejemplo 🙂

    @carlos, al final el contenedor IoC a elegir es casi cuestión de preferencias personales para usos habituales como el mostrado aquí. En Unity también se controla la vida per-request de las dependencias.

    Gracias a todos por comentar!

Responder a jmaguilar Cancelar respuesta

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