Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 20 de octubre de 2015
ASP.NET CoreEstá claro que uno de los secretos para la creación de aplicaciones web de alto rendimiento es el uso apropiado del caché, y por esta razón todos los frameworks incorporan herramientas que hacen posible almacenar información que pueda ser reutilizadas para acelerar la respuesta de peticiones posteriores, como porciones de página o resultados de procesos costosos.

En ASP.NET 4.x y anteriores, siempre podíamos acceder a objeto Cache disponible en el contexto de la petición, o a los componentes presentes en System.Web.Caching y crear nuestras soluciones personalizadas, pero realmente MVC no aportaba más ayudas de serie que el filtro [OutputCache]. Su objetivo era cachear el resultado de acciones durante un tiempo determinado y reutilizarlo en peticiones siguientes, lo que era suficiente para muchos escenarios pero complicaba un poco otras necesidades comunes, como el cacheo de porciones de páginas.

Pero antes de continuar, un par de avisos:
  • Si aún no sabes lo que es un tag helper, ya estás tardando en leer este post ;)
  • ASP.NET se encuentra aún en desarrollo, por lo que parte de lo expuesto a continuación podría variar en la versión final del producto.
Y ahora, al tema. ASP.NET Core MVC incorpora un tag helper, llamado CacheTagHelper, que permite delimitar un bloque de código de vista mediante el tag <cache>. El resultado de renderizar su contenido será cacheado en el servidor y reutilizado las siguientes ejecuciones de la vista, hasta que la caché expire por las condiciones que hayamos indicado.

Veamos un ejemplo de la forma más simple que tenemos de utilizar este tag helper en una vista:
<h2>Current: @DateTime.Now.ToString("hh:mm:ss"))</h2>
<cache expires-after="TimeSpan.FromSeconds(30)">
    <h2>Cached: @DateTime.Now.ToString("hh:mm:ss")</h2>
</cache>
Hay varios aspectos interesantes en este código. Primero, observad que este tag helper define una etiqueta nueva, a diferencia de otros que hemos visto, como AnchorTagHelper para construir enlaces <a> . Debido a ello, no hay necesidad de distinguir sus parámetros con un prefijo, como los habituales "asp-" que vemos en otros tag helpers que modifican etiquetas existentes.

Otro aspecto a destacar es que hemos especificado el valor del parámetro expires-after directamente mediante una expresión C#, sin necesidad de utilizar el carácter de escape "@" de Razor. Esto es así porque el parser ha detectado que la propiedad subyacente es de tipo TimeSpan, por lo que directamente espera que codifiquemos ahí una expresión de este tipo, y el entorno puede ayudarnos con el imprescindible intellisense.

Si accedemos a esta vista y refrescamos la página segundos más tarde, el resultado que tendremos es el siguiente. Como podemos ver, el bloque del interior de <cache> no será evaluado de nuevo hasta pasados los 30 segundos que hemos indicado en el parámetro expires-after, retornando en su lugar el resultado que fue cacheado anteriormente:


image

image

Ejecución inicial


Tras refrescar la página


Otro ejemplo. En este caso, la primera ejecución de la página durará cinco segundos, pero tras ella todas las demás retornarán inmediatamente el contenido cacheado sin necesidad de ejecutar nada:
<cache expires-after="TimeSpan.FromMinutes(10)">
    @{
        await Task.Delay(5000);
    }
    <h2>Cached: @DateTime.Now.ToString("hh:mm:ss")</h2>
</cache>
La etiqueta <cache> tiene parámetros que permiten configurar su comportamiento de forma muy similar al filtro [OutputCache], y que seguro que os son familiares:
  • Parámetros para indicar la duración del caché:

    • expires-after, visto anteriormente en los ejemplos, indica el tiempo de validez del caché desde que se almacena su contenido.

    • expires-sliding, contiene un TimeSpan que indica el tiempo en que el caché caducará tras haber accedido a su contenido por última vez.

    • expires-on, un valor DateTimeOffset que indica en términos absolutos cuándo expirará el contenido cacheado.
      <cache expires-sliding="TimeSpan.FromSeconds(30)">
          <h2>Cached: @DateTime.Now.ToString("hh:mm:ss")</h2>
      </cache>

  • Parámetros que indican qué factores actúan como criterios de discriminación a la hora de salvar o recuperar valores de la caché. Esto es bastante útil si queremos cachear porciones de página para determinado grupo de usuarios o peticiones:

    • var-by, permite que el caché varíe dependiendo del valor alfanumérico arbitrario del mismo.

    • var-by-header, nombre del encabezado HTTP que variará el caché.

    • var-by-query, una lista de parámetros del query string, separados por comas, que actuarán como discriminador.
      <cache expires-after="TimeSpan.FromMinutes(10)" var-by-query="searchTerm">
          <h2>Search results:</h2>
          <ul>
          @foreach(var product in Products)
          {
              <li>@product.Name<li>
          }
          </ul>
      </cache>
    • var-by-route, una lista de parámetros de ruta que actuarán como discriminador.

    • var-by-cookie, lista de nombres de cookies separados por comas, que actuarán como dis

    • var-by-user, un booleano que indica si el valor de la caché debe variar en función de la identidad del  usuario conectado.
      <cache expires-after="TimeSpan.FromMinutes(10)" var-by-user="true">
          <h2>Hello, @User.Identity.Name </h2>
      </cache>

  • Parámetros generales de comportamiento:

    • priority , un valor CacheItemPriority que indica la prioridad del contenido en situaciones de escasa memoria.

    • enabled, que si establecemos a "false" permite desactivar el caché, forzando a que se procese el contenido del tag <cache> como si no existiera. Esto puede ser útil, por ejemplo, para depurar una página:
      <cache expires-after="TimeSpan.FromMinutes(10)" enabled="false">
          <h2>Cached: @DateTime.Now.ToString("hh:mm:ss")</h2>
      </cache>
Y básicamente, esto es todo lo que hay que conocer para sacar partido a este tag helper, que la verdad es que es bastante sencillito y puede simplificar algunos escenarios. Eso sí, como siempre, usadlo con precaución: además de las confusiones que puede aportar una caché a la depuración, hay que tener cuidado con las sutiles dependencias que crearíamos desde la vista hacia factores externos a la misma, como las cookies, la tabla de rutas o el query string si usamos parámetros como vary-by-cookie, vary-by-query o vary-by-route.

Ah, y una última cosa: al menos de momento,  CacheTagHelper usa internamente para almacenar el caché una instancia de IMemoryCache, por lo que el contenido no puede ser distribuido o enviado a un almacenamiento externo como Redis o SQL Server, ni será persistente si el servidor sufre algún tropezón.

Publicado en Variable not found.

3 Comentarios:

Julio A dijo...

Hola José, que gran post y que claro! Ahora me surge una duda, cuando usamos expires-after y expires-sliding en el mismo tag helper, cual tiene prioridad en caso que se "sobre ponga" el momento en que debe vencer el caché?

Saludos!

José María Aguilar dijo...

Hola, Julio!

A día de hoy, si en el mismo helper se indican ambos parámetros, expires-after ganaría pues indica la caducidad en términos absolutos del contenido.

Saludos & gracias por comentar!

Julio A dijo...

Gracias José, eso mismo pensé, pero mejor estar seguros, seriá interesante que más adelante se incluya la posiblidad de usar componentes externos para guardar dicha caché como Redis, SQL u otros... aunque claro, esto es Open Soure, siempre es posible hacer un PR :)

Saludos!