Inyección de dependencias per-request en MVC4 y WebApi

¡Muy buenas! Si desarrollais una aplicación web con MVC4 o bien una API REST con WebApi y usáis, pongamos, EF para acceder a la BBDD ya sabréis (y si no, os lo cuento ahora :P) que lo ideal es que el tiempo de vida del DbContext sea el de toda la petición web (lo mismo aplica a ISession si usáis NHibernate, por supuesto).

En muchos ejemplos y blogs se lee código parecido al siguiente (p.ej. en un repositorio/dao):

public IEnumerable<MyEntity> GetAll()

{

    using (_context = new MyDbContext())

    {

        return _context.MyEntities.ToList();

    }

}

Este código, aunque funciona, no es del todo correcto: Cada llamada a uno de esos métodos crea y destruye un contexto de Entity Framework. Sin entrar en consideraciones de rendimiento, esto hace que si dentro de una misma acción de un controlador llamamos a dos métodos de este repositorio (o bien de repositorios distintos) se crearán dos contextos de EF con todo lo que ello conlleva.

Por supuesto, irnos al extremo contrario, y guardar el contexto en una variable estática y abrirlo en el Application_Start y cerrarlo en Application_End es una idea terrible… para empezar todos los usuarios estarían compartiendo el mismo contexto de base de datos y además DbContext no es thread-safe (tampoco ISession de NHibernate lo es, si ya corrías a descargartelo).

¿Y guardar el contexto en una variable de sesión? ¡Peor! Entonces tendrías un contexto abierto por cada usuario… idea terrible, pues si tu aplicación crece y tiene bastantes usuarios conectados a la vez, eso tumbaría tu base de datos. Además de que no hay nada que te garantice que todas las peticiones de un mismo usuario sean procesadas en un mismo thread.

¿La solución? Crear un contexto de BBDD por cada petición y cerrarlo una vez la petición se termina. De este modo, si dentro de la acción de un controlador llamas a 4 repositorios (por decir algo), esos 4 repositorios compartirán la instancia del contexto de bbdd. Al abrir y cerrar el contexto en cada petición podemos asegurar que no nos quedan contextos abiertos. A estos objetos que se crean y se destruyen una vez por cada petición, les decimos que tienen un tiempo de vida per-request.

Bien… veamos como podemos conseguir esto tanto en MVC4 como en WebApi, usando el contenedor IoC de Microsoft: Unity (en su versión 2.1).

Si no conoces nada acerca del funcionamiento de Unity, he escrito varios posts sobre él en este mismo blog.

A. Solución de MVC4

MVC4 viene bastante bien preparada para IoC, de hecho hay varias maneras en como se puede configurar la inyección de dependencias en MVC4. Debemos tener presente que Unity, a diferencia de otros contenedores IoC no es capaz de gestionar automáticamente objetos con un tiempo de vida per-request, así que nos lo tendremos que currar nosotros, aunque no es excesivamente dificil. Para “simular” el tiempo de vida per-request, nos vamos a aprovechar de la característica de “contenedor hijo” que tiene Unity. En Unity, a partir de un contenedor padre, podemos crear un contenedor hijo, que obtiene todos los mapeos de tipo del padre y puede añadir los suyos. Cuando se destruye este contenedor hijo, todos los mapeos nuevos que tenga dicho contenedor son destruídos, y se llama a Dispose() de todos los objetos singleton gestionados por este controlador hijo (que sean IDisposable).

A.1 Crear un contenedor hijo en cada inicio de petición y destruirlo al final

Ah… eso es fácil, ¿no? Nos basta usar Application_BeginRequest:

protected void Application_BeginRequest()

{

    var childContainer = _container.CreateChildContainer();

    HttpContext.Current.Items[_unityGuid] = childContainer;

}

Y Application_EndRequest, por supuesto:

protected void Application_EndRequest()

{

    var childContainer = HttpContext.Current.Items[_unityGuid] as IUnityContainer;

    if (childContainer != null)

    {

        childContainer.Dispose();

        HttpContext.Current.Items.Remove(_unityGuid);

    }

}

Por si te lo preguntas _container es el contendor de Unity principal y _unityGuid no es nada más que un objeto para usar como clave en HttpContext. Ambos se inicializan en Application_Start y son estáticos:

private static Guid _unityGuid;

private static IUnityContainer _container;

 

protected void Application_Start()

{

    _container = new UnityContainer();

    _unityGuid = Guid.NewGuid();

A.2 Mapear los tipos en el contenedor de Unity

Eso tampoco reviste especial complicación. La idea es:

  • Los singleton compartidos por todos los usuarios se declaran con el LifetimeManager ContainerControlledLifetimeManager en el contenedor principal
  • Los objetos de crear y destruir (p. ej. un repositorio o un controlador) se declaran con el LifetimeManager TransientLifetimeManager en el contenedor princial
  • Los objetos per-request (como el contexto de EF) se declaran con el LifetimeManager HierarchicalLifetimeManager en el controlador principal. Otra opción sería declararlos como singletons (ContainerControlledLifetimeManager) en el contenedor hijo.

Nota: El lifetime manager HierarchicalLifetimeManager es equivalente al ContainerControlledLifetimeManager (es decir existe una sola instancia en el contenedor) pero con la salvedad de que los contenedors hijos no comparten la instancia del contenedor padre, si no que cada contenedor hijo puede tener la suya propia.

Sería algo así como:

// Objetos «de usar y tirar»:

container.RegisterType<IMyRepo, MyRepository>();

// Contexto EF

container.RegisterType<MyDbContext>(new HierarchicalLifetimeManager());

A.3 Crear el “activador de controladores” (IControllerActivator)

Una vez configurado el contenedor debemos decirle a MVC4 que lo use. Una de las formas de hacerlo es crear un IControllerActivator. Esto nos basta para la mayoría de casos (cuando tan solo vamos a inyectar dependencias en los controladores, lo que es ásí casi siempre). ´

La idea es crear los controladores usando el controlador hijo que hemos guardado en HttpContext.Items:

public class UnityControllerActivator : IControllerActivator

{

 

    private Guid _containerGuid;

    public UnityControllerActivator(Guid containerGuid)

    {

        _containerGuid = containerGuid;

    }

    public IController Create(RequestContext requestContext, Type controllerType)

    {

        var container = requestContext.HttpContext.Items[_containerGuid] as IUnityContainer;

        if (container != null)

        {

            return container.Resolve(controllerType) as IController;

        }

        return null;

    }

}

Fijaos que simplemente llamamos a “Resolve” del contenedor que obtenemos de HttpContext.Items.

A.4 Configurar MVC4 para que use nuestro activador de controladores

Para que MVC4 use nuestro activador de controladores, debemos implementar un dependency resolver. A diferencia de la factoría de controladores p.ej, que se puede establecer a través de una clase estática), no hay ningún mecanismo para configurar que activador de controladores usará MVC. Si queremos usar uno específico (como es nuestro caso) debemos establecer un dependency resolver.

Para ello lo suyo es crearse uno que use el propio contenedor de Unity:

public class MvcConfig

{

    public static void Register(IUnityContainer container, Guid containerGuid)

    {

        container.RegisterInstance(typeof (IControllerActivator), new UnityControllerActivator(containerGuid));

        foreach (var type in

            Assembly.GetExecutingAssembly().GetExportedTypes().

                        Where(x => x.GetInterface(typeof(IController).Name) != null))

        {

            container.RegisterType(type);

        }

        DependencyResolver.SetResolver(

            t => container.IsRegistered(t) ? container.Resolve(t) : null,

            t => container.IsRegistered(t) ? container.ResolveAll(t) : Enumerable.Empty<object>());

    }

}

La llamada al método MvcConfig.Register iría en el Application_Start una vez creado el contenedor principal. Este método hace 3 cosas:

  1. Registra el activador de controladores como singleton en el contenedor.
  2. Registra todos los controladores que haya
  3. Finalmente crea el dependency resolver que usa el propio contenedor.

Con esto ya podemos inyectar las dependencias en nuestros controladores y en nuestros repositorios (a los controladores les inyectaríamos los repositorios y a los repositorios el contexto de EF).

B. Solución WebApi

Aunque, por oscuras razones, WebApi se distribuye junto a MVC y parece mucho lo mismo en el fondo hay muchas diferencias entre MVC y WebApi. Y una de esas diferencias es como funciona el tema de inyección de dependencias. Por lo tanto lo que hemos dicho de MVC no funciona para WebApi.

Si queremos obtener lo mismo (es decir que el contexto de EF tenga un tiempo de vida per-request) con WebApi, debemos seguir una estrategia distinta…

Lo que sí es igual es la configuración del contenedor (se siguen las mismas directrices que el punto A.2).

B.1 Crear un DependencyResolver de WebApi

Al igual que en MVC, necesitamos un dependency resolver, pero el de WebApi es distinto que el de MVC. Así si en MVC usábamos el método estático SetResolver de la clase DependencyResolver, en WebApi usaremos la propiedad DependencyResolver del objeto HttpConfiguration global, al que podemos acceder a través de GlobalConfiguration.Configuration.

A diferencia de MVC donde podemos pasar directamente dos delegates para crear el dependency resolver, en WebApi estamos obligados a crear una clase que implemente IDependencyResolver.

Además WebApi, a diferencia de MVC, ya viene “más o menos” preparado para el concepto de objetos per-request. Existe un método dentro de IDependencyResolver, llamado BeginScope. La idea es que cada vez que se necesite crear un “scope hijo” se llame a este método. Lo bonito sería que el framework lo hiciese por nosotros al crear un controlador, pero la realidad es que no lo hace. Nos tocará hacerlo nosotros.

Así para implementar IDependencyResolver vamos a usar dos clases:

public class UnityDependencyScope : IDependencyScope

{

    private IUnityContainer _container;

    protected IUnityContainer Container { get { return _container; } }

    public UnityDependencyScope(IUnityContainer container)

    {

        _container = container;

    }

    public void Dispose()

    {

        _container.Dispose();

    }

    public object GetService(Type serviceType)

    {

        return _container.IsRegistered(serviceType)

                    ? _container.Resolve(serviceType)

                    : null;

    }

    public IEnumerable<object> GetServices(Type serviceType)

    {

        return _container.ResolveAll(serviceType);

    }

}

Esta primera clase implementa IDependencyScope. Esta interfaz es la interfaz base de IDependencyResolver. De hecho IDependencyResolver añade tan solo el método BeginScope. La segunda clase es la que implementa IDependencyResolver:

public class UnityDependencyResolver : UnityDependencyScope, IDependencyResolver

{

    public UnityDependencyResolver(IUnityContainer container) : base (container)

    {

    }

    public IDependencyScope BeginScope()

    {

        return new UnityDependencyScope(Container.CreateChildContainer());

    }

}

Implementamos el método BeginScope retornando un contenedor hijo de nuestro contenedor. De esta manera ya podemos “encadenar” scopes hijos.

B.2 Crear el activador de controladores

Al igual que en MVC debemos crear un activador de controladores propio. Y por supuesto es distinto del de MVC 🙂

En este caso debemos implementar IHttpControllerActivator:

public class UnityHttpControllerActivator : IHttpControllerActivator

{

    private IUnityContainer _container;

    public UnityHttpControllerActivator(IUnityContainer container)

    {

        _container = container;

    }

    public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)

    {

        var scope = request.GetDependencyScope();

        return scope.GetService(controllerType) as IHttpController;

    }

}

La clave está en la llamada a GetDependencyScope del parámetro request. Esto creará un scope (llamando a BeginScope) por lo que el controlador que creemos se creará con el contenedor Unity hijo.

B.3 Configurar WebApi para que use nuestro activador de controladores

Eso si que es realmente sencillo. Simplemente registramos el activador de controladores y el dependency resolver en WebApi. Esto lo hacemos en el Application_Start, p.ej. en el propio método Register de la clase WebApiConfig que crea Visual Studio:

public static void Register(HttpConfiguration config, IUnityContainer container)

{

    config.DependencyResolver = new UnityDependencyResolver(container);

    container.RegisterInstance<IHttpControllerActivator>(new UnityHttpControllerActivator(container));

    foreach (var type in

        Assembly.GetExecutingAssembly().GetExportedTypes().

                    Where(x => x.GetInterface(typeof(IHttpController).Name) != null))

    {

        container.RegisterType(type);

    }

}

También debemos registrar todos los controladores de WebApi (de una forma análoga a la que usábamos al registrar los controladores MVC).

¿Y si tengo WebApi junto a MVC en un mismo proyecto?

Pues te tocará hacer todo lo de MVC y todo lo de WebApi. Lo único que no haces dos veces es la configuración del contenedor (punto A.2) ya que es la misma.

Eso sí, en este caso puedes hacer que si la petición HTTP es una llamada a WebApi, en lugar de una llamada a un controlador MVC, no se cree el contenedor hijo en Application_BeginRequest (ni se destruya en Application_EndRequest). Algo como:

protected void Application_BeginRequest()

{

    if (!Request.IsWebApiRequest())

    {

        // Código para MVC

    }

}

El método IsWebApiRequest es un método de extensión que me he inventado. La verdad, no tenía nada claro como diferenciar una petición de WebApi de una de MVC así que he cortado por lo sano:

public static class HttpRequestExtensions

{

    public static bool IsWebApiRequest(this HttpRequest @this)

    {

        return @this.FilePath.StartsWith(«/api/»);

    }

}

Exacto… Si la URL empieza por /api/ es que es una llamada a WebApi. Ya… cutre, pero oye tu: funciona 😛 Por supuesot, dependiendo de la tabla de rutas que tengas eso puede no ser cierto en tu caso 😉

Y nada más… con eso conseguimos un tiempo de vida per-request tanto en ASP.NET MVC4 como con WebApi 🙂

Saludos!

11 comentarios sobre “Inyección de dependencias per-request en MVC4 y WebApi”

  1. Muy bien explicado, como siempre. No sé mucho de ASPNET MVC ni WebApi, pero viendo esto me da la sensación de que hay cosas que deberían ser más simples.

    Me gusta la idea de usar child containers para poder «disposear» de forma controlada lo que se ha creado en un request. Como el IDependencyResolver no tiene un método Release, no hay muchas formas limpias de hacerlo.

    Por otra parte, ¿no se podría haber implementado sólo un IDependencyResolver en lugar de un IControllerActivator?

  2. Excelente artículo Eduart, se me ocurren varias preguntas, se supone que los contextos de Entity framework han sido optimizados para abrirse y cerrarse varias de veces utilizando un sistema de cache y almacenando el mapeo de los metadatos a nivel del dominio de la aplicación y de la vista abstracta de la base de datos, con lo que el coste del instanciar el contexto se produce solo la primera vez, así que la acción por controlador se supone que utilizaría dicha cache no???, quiero también preguntarte sobre el coste de la inyección de dependencias pues entiendo que el sistema tiene que instanciar las clases utilizando reflexión y para un contexto de datos grande esto puede convertirse en un problema de rendimiento en entornos Web con muchos accesos ¿?

    Un saludo.

  3. Eduard, la verdad es que lo he leido en diagonal, pero me parece demasiado ‘rebuscado’.Aunque entiendo que un per-request en asp.net es no trivial por su modelo de ejecucion, creo que para el caso que has mostrado un simple,per-resolve y un controller factory podria ser suficiente…algo similar a este ejemplo de Mark Seeman con web.api y con una elegante solucion al release..

    http://blog.ploeh.dk/2012/10/03/DependencyInjectionInASPNETWebAPIWithCastleWindsor.aspx

    Porque te leo se que seguro querrias ir mas alla y por la lectura rapida no lo he visto(aeropuerto)..

    UNAI

  4. Buenas!
    Gracias a todos por los comentarios!

    @Juanma
    Necesito el IControllerActivator para que MVC me use el contenedor hijo. En MVC si no creo el IControllerActivator MVC me usará siempre el contenedor padre (a no ser que mi dependency resolver se encargase de usar el contenedor hijo, algo que no he hecho y que sería posible hacerlo también). Por lo tanto, sí: tan solo con el Dependency Resolver pasaría, pero no con la implementación que he puesto en el post.

    @Juan
    No te preocupes por la instanciación via reflection. Es tan solo la instanciación del contexto: no veo porque tiene que verse afectado por el tamaño (entiendo que te refieres a # de colecciones y demás) de éste.

    @Unai
    Me miro la solución de Mark… Me parece que sí, que es más simple, gracias al uso de RegisterForDispose, con lo que entonces me ahorraría el DependencyResolver. Gracias por la referencia.

    @Carlos
    Sí… hay otros contenedores mejor preparados que Unity para esto, pero bueno 😀

    Saludos!

  5. Eduard, muy bueno… Me parece una implementación muy buena…

    Simplemente genial, ya no solo por la implementación sino por explicar el modelo de ejecución e interioridades de ASP.Net

  6. Gran artículo Eduard!!..

    Solo una consulta..

    1- Unity.Mvc3 funciona perfectamente con MVC4
    2- Unity.WebApi funciona perfectamente con WebApi
    3- Unity.Mvc3 y Unity.WebApi funcionan perfectamente juntos…

    Conoces de algún problema en el uso de esas extensiones?

    Gracias!

  7. ¡Excelente trabajo! Me gustaría ver un tema sobre seguridad y atenticación con WebAPI, me explico… Que solo se pueda acceder a un recurso si este tiene ciertas credenciales.

    Gracias de antemano.

  8. Hola Eduard, estoy usando tu estrategia en una aplicación mixta (MVC y WebAPI). Una de mis paginas tiene cerca de 10 request al webapi para diferentes datos que requiero en la interfaz … Lamentablemente casi siempre recibo errorres como este en uno o dos del los request enviados:

    * The context cannot be used while the model is being created
    * The connection was not closed. The connection’s current state is connecting
    * The type ‘XXXX’ has been mapped more than once.

    Mi aplicación esta compuesta de una capa de modelo (EF), una capa de repositorios, y una capa de servicios que convierten las entidades en POCOs ….

    He estado varios dias intentando encontrar que estoy haciendo mal, pero no logro encontrar el problema. De casualidad tienes alguna sugerencia?

Responder a omarvr Cancelar respuesta

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