This page looks best with JavaScript enabled

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

 ·  ☕ 10 min  ·  ✍️ eiximenis

Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí

¡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!

Si quieres, puedes invitarme a un café xD

eiximenis
ESCRITO POR
eiximenis
Compulsive Developer