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, 17 de enero de 2023
ASP.NET Core

Desde la llegada de ASP.NET Core, hace ya algunos años, muchos hemos echado de menos el filtro [OutputCache] de ASP.NET MVC "clásico". Aunque el nuevo framework ofrece alternativas interesantes para gestionar la caché tanto en el lado cliente como en el servidor, ninguna aportaba las funcionalidades que este filtro nos ofrecía.

Como recordaréis, a diferencia de las opciones ofrecidas actualmente por ASP.NET Core, como el filtro [ResponseCache] o el middleware ResponseCaching, que básicamente se regían por los encabezados presentes en peticiones y respuestas HTTP, el filtro [OutputCache] es una solución de caché residente exclusivamente en el servidor. En este caso, la decisión de si el resultado a una petición se almacena o no se realiza completamente desde la aplicación, de forma totalmente independiente a encabezados o requisitos procedentes del lado cliente.

En ASP.NET Core 7 este filtro ha vuelto a la vida en forma de middleware, que ofrece sus funcionalidades con dos sabores distintos:

  • Con anotaciones aplicables a endpoints implementados con Minimal API.
  • Como filtro, aplicable a controladores y acciones MVC.

Echémosles un vistazo.

El middleware OutputCache

La utilización del middleware OutputCacheMiddleware requiere que registremos sus servicios en el inyector de dependencias:

var builder = WebApplication.CreateBuilder(args);
builder.services.AddOutputCaching();
...

Tras ello, podemos insertar el middleware en el pipeline usando el típico extensor:

var app = builder.Build();
app.UseOutputCache();
...

Ojo: ¡el orden importa! UseOutputCache() debe usarse después de UseCors() y UseRouting() (si existen), para que funcione correctamente.

Una vez posicionado en el pipeline el middleware examinará cada petición entrante y determinará si ésta puede ser resuelta directamente desde el contenido almacenado en caché.

  • En caso afirmativo, retornará el contenido directamente al lado cliente sin llegar a ejecutar el handler que debería procesarla.
  • En caso negativo, el middleware delegará la petición al manejador correspondiente, que, además de generar la respuesta, tendrá la capacidad de decidir si la misma debe ser almacenada en caché o no y de qué forma.

Cacheo de resultados en endpoints minimal API

En endpoints definidos mediante minimal APIs, podemos utilizar el extensor CacheOutput() para indicar la política de cacheado a aplicar en ese punto.

El siguiente ejemplo usa la configuración por defecto para cachear durante un minuto el resultado de las llamadas a la raíz de la aplicación web:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOutputCache();

var app = builder.Build();
app.UseOutputCache();

app.MapGet("/", () => DateTime.Now)
    .CacheOutput();

app.Run();

El extensor CacheOutput() dispone de varias sobrecargas, mediante las cuales podemos ser más específicos a la hora de configurar el cacheado de los resultados. La más sencilla consiste en usar un delegado de tipo OutputCachePolicyBuilder en cuyo interior podremos configurar el comportamiento del caché usando distintos métodos de utilidad.

Por ejemplo, en el siguiente código podemos ver el uso de Expire() para indicar que el resultado del endpoint debe ser cacheado durante cinco segundos:

app.MapGet("/5secs", () => DateTime.Now)
   .CacheOutput(p => p.Expire(TimeSpan.FromSeconds(5)));

O podemos configurar algo más complejo, como en el siguiente ejemplo donde, además de especificar el tiempo de expiración, indicamos que la caché debe tener en cuenta el valor de un parámetro determinado de la query string y de un encabezado, con objeto de que no se mezclen las respuestas entre distintos usuarios y distintas búsquedas:

app.MapGet("/search", (string q) => {
    // Retornar resultados de búsqueda
}).CacheOutput(p => p
    .Expire(TimeSpan.FromSeconds(5))
    .SetVaryByHeader("authorization")
    .SetVaryByQuery("q")
);

Cacheo de resultados de acciones MVC

Aquí es donde realmente estaremos ante la reencarnación del clásico filtro [OutputCache], pues podremos utilizarlo poco más o menos como hacíamos en versiones anteriores de MVC.

En MVC, basta con aplicar este filtro a acciones (o controladores, si queremos que se vean afectadas todas sus acciones) indicando las políticas de cacheo a aplicar mediante sus parámetros. El ejemplo más simple sería el siguiente, donde se cacheará el resultado de la acción Index() durante el tiempo por defecto de un minuto:

public class HomeController : Controller
{
    [OutputCache]
    public DateTime Index()
    {
        return DateTime.Now;
    }
}

Para cambiar el tiempo de expiración, podemos usar el parámetro Duration, donde indicaremos el número de segundos que queremos mantener el resultado de la acción en caché:

[OutputCache(Duration = 5)]
[HttpGet("/5secs")]
public DateTime FiveSecs()
{
    return DateTime.Now;
}

Y de la misma forma que vimos antes, podemos usar parámetros como

[OutputCache(Duration = 5, VaryByQueryKeys = new [] {"query"}, VaryByHeaderNames = new[] {"authorization"})]
public IActionResult Search(string query)
{
    // Retornar resultados de búsqueda
}

Hay mucho más que contar sobre el nuevo output caching, como el uso de políticas para simplificar las configuraciones, la posibilidad de usar orígenes distintos a la memoria para almacenar los resultados (p.e. Redis), o el bloqueo del "efecto estampida" pero eso lo dejaremos para ocasiones posteriores ;)

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!