domingo, 26 de octubre de 2014

OutputCache con MvcDonutCaching y MongoDB

Si hablamos de cache en una aplicación ASP.NET MVC, una de las primeras opciones a valorar es utilizar el atributo OutputCache para cachear la salida de los controladores.

Aunque a priori OutputCache podría satisfacer nuestros requerimientos, lo cierto es que adolece de algunos problemas que hacen que uso no sea todo lo satisfactorio que cabría esperar.

Muchos de estos problemas están relacionados con el uso del atributo en acciones hijas (las que son invocadas con Html.Action). En concreto, encontramos las siguientes limitaciones cuando hablamos de acciones hijas:

  • Sólo soportan las propiedades Duration, VarByCustom y VarByParam. Lo más relevante aquí es que no soporta la propiedad CacheProfile y, por ende, no podemos establecer una política de expiración de caché a través del web.config.
  • Ignora por completo el valor del atributo enableOutputCache de la sección outputCache del web.config. Esto significa que algo tan habitual como jugar con este valor para activar o desactivar la cache en desarrollo, no funcionará con acciones hijas.

En cualquier caso, no sólo las acciones hijas presentan limitaciones. Si pensamos en qué querríamos cachear en el contexto de la salida de ASP.NET MVC, encontraríamos fácilmente los siguientes escenarios:

  • Cachear la página entera (Full page caching). Soportado puesto que cachear una acción padre engloba cachear todo el resultado del método de acción.
  • Cachear pequeños fragmentos de la página (también llamado cachear el agujero del donut, Donut hole caching). Igualmente soportado a través de la cache de acciones hijas.
  • Cachear la página entera excepto pequeños fragmentos (lo que se llama cachear el donut pero no el agujero, Donut caching). Esto NO está soportado por el proveedor de serie aun cuando, si recordamos, se podía hacer por ejemplo en WebForms a través del control Substitution.

Otro problema grave que nos encontramos es que invalidar la cache no es algo que esté muy conseguido. Sólo tenemos disponible un triste método Response.RemoveOutputCacheItem(path) que sólo funciona para acciones padre y además no permite invalidar de una sola vez todas las versiones de una página, es decir, tendremos que llamar a este método tantas veces como distintas versiones de path haya que incluyan parámetros como segmentos en el path.

El cómo solventar todos estos problemas de un plumazo es sencillo, utilizando el paquete MvcDonutCaching

Este paquete nos brinda un nuevo atributo llamado DonutOutputCache, que sustituye por completo a OutputCache y hace que todo funcione como debería funcionar. Ahora no hay distinción entre acciones padres e hijas, todo funciona (las acciones hijas ya pueden utilizar CacheProfile, están al tanto de enableOutputCache, etc.) y además nos permite cachear el donut (Donut caching). En este sentido, MvcDonutCaching lo pone muy fácil porque agrega sobrecargas al helper Html.Action para poder especificar si la llamada a la acción hija debe excluirse de la cache de su padre, por ejemplo en vez de escribir @Html.Action(“Acción”), si escribimos @Html.Action(“Accion”, true) ya estaríamos cacheando el donut.

MvcDonutCaching además también permite invalidar la caché de forma precisa con los métodos RemoveItem y RemoveItems de la clase OutputCacheManager.

En este punto ya somos más felices, pero si cabe (y si es necesario) podríamos serlo un poco más. Esto es debido a que a partir de .NET 4.0 se introdujo la posibilidad de crear nuestro propio proveedor de cache personalizado y configurarlo vía web.config. Siendo así podemos hacer que nuestra cache persista en un almacenamiento compartido permitiendo que varios frontales compartan la misma cache e incluso que la cache sobreviva a reinicios de la aplicación. En mi caso he optado por crear un proveedor de cache basado en MongoDB.

Para ello hay que crear una clase que herede de OutputCacheProvider, implemente los métodos necesarios y configurar el nuevo proveedor vía web.config.

El código del proveedor está disponible en github https://github.com/panicoenlaxbox/MvcDonutCaching.MongoDBOutputCache y también hay disponible paquete de Nuget para instalarlo de forma sencilla http://www.nuget.org/packages/MvcDonutCaching.MongoDBOutputCache/

Resumiendo, los pasos para configurar el cache como hemos explicado son:

1. Instalar el paquete MvcDonutCaching

2. Instalar el paquete MvcDonutCaching.MongoDBOutputCache

3. Configurar el proveedor en el web.config

    <caching>

      <outputCache defaultProvider="AspNetMongoDB">

        <providers>

          <add name="AspNetMongoDB" type="MongoDBOutputCache.MongoDBOutputCacheProvider, MongoDBOutputCache" />

        </providers>

      </outputCache>

    </caching>

4. Agregar las siguientes claves en AppSettings (lógicamente apuntando a la instancia de MongoDB que quieras utilizar – con o sin autenticación – y especificando la base de datos que quieres utilizar):

    <add key="MongoDBOutputCacheProviderConnectionString" value="mongodb://admin:admin@localhost/aspnet" />

    <add key="MongoDBOutputCacheProviderCollection" value="OutputCache" />

Y ahora sí, tenemos una cache de salida de ASP.NET MVC como hubiésemos querido en un principio!

Un saludo!

No hay comentarios:

Publicar un comentario