Skip to content
Perspectivas e Iteraciones Entendiendo la IA: técnico, cotidiano y reflexiones.
← atrás

Implementando Caché de Solicitudes en ASP.Net Core

7 de julio de 2019 • 4 min de lectura

Implementando Caché de Solicitudes en ASP.Net Core

En algún momento del desarrollo de una aplicación, generalmente bastante temprano, te das cuenta de que la aplicación es lenta. Después de investigar un poco, el culpable es recuperar innecesariamente los mismos datos, y se enciende una luz, y piensas: “Necesito algo de caché.”

El caché es un patrón invaluable para eliminar llamadas redundantes a una base de datos o a una API de terceros. Microsoft proporciona IMemoryCache para caché basado en tiempo, sin embargo, a veces el caché basado en tiempo no es lo que necesitas. En este artículo, analizamos el caché con alcance de solicitud y cómo puede beneficiarnos.

¿Qué es el caché de solicitudes? El caché de solicitudes es un mecanismo para almacenar datos en caché durante la vida de una solicitud web. En dot-net, hemos tenido esta capacidad en cierta medida con la colección HttpContext.Items, sin embargo, HttpContext no es conocido por su capacidad de inyección.

El caché con alcance de solicitud tiene algunos beneficios: Primero, elimina la preocupación por datos obsoletos. En la mayoría de escenarios, una solicitud se ejecuta en menos de un segundo, lo cual típicamente no es suficiente tiempo para que los datos se vuelvan obsoletos. Y segundo, la expiración no es una preocupación porque los datos mueren cuando termina la solicitud.

De forma predeterminada, Asp.Net Core no tiene caché inyectable. Como se mencionó anteriormente, HttpContext.Items es una opción, pero no es una solución elegante.

Afortunadamente para nosotros, ASP.Net Core nos da las herramientas para crear una implementación de caché de solicitudes inyectable utilizando el framework de inyección de dependencias (DI) incorporado.

El framework DI incorporado tiene tres tiempos de vida para las dependencias: Singleton, Scoped, y Transient. Singleton es para la vida de la aplicación, Scoped es para la vida de la solicitud y Transient es una nueva instancia con cada solicitud.

He creado una interfaz modelada según la interfaz IMemoryCache para mantener las cosas consistentes.

Interfaz

public interface IRequestCache
{
    /// <summary>
    /// Add the value into request cache. If the key already exists, the value is overwritten.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <typeparam name="TValue"></typeparam>
    void Add<TValue>(string key, TValue value);

    /// <summary>
    /// Remove the key from the request cache
    /// </summary>
    /// <param name="key"></param>
    void Remove(string key);

    /// <summary>
    /// Retrieve the value by key, if the key is not in the cache then the add func is called
    /// adding the value to cache and returning the added value.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="add"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    TValue RetrieveOrAdd<TValue>(string key, Func<TValue> add);

    /// <summary>
    /// Retrieves the value by key. When the key does not exist the default value for the type is returned.
    /// </summary>
    /// <param name="key"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    TValue Retrieve<TValue>(string key);
}

Implementación

public class RequestCache : IRequestCache
{
    IDictionary<string, object> _cache = new Dictionary<string, object>();

    /// <summary>
    /// Add the value into request cache. If the key already exists, the value is overwritten.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <typeparam name="TValue"></typeparam>
    public void Add<TValue>(string key, TValue value)
    {
        _cache[key] = value;
    }

    /// <summary>
    /// Remove the key from the request cache
    /// </summary>
    /// <param name="key"></param>
    public void Remove(string key)
    {
        if (_cache.ContainsKey(key))
        {
            _cache.Remove(key);
        }
    }

    /// <summary>
    /// Retrieve the value by key, if the key is not in the cache then the add func is called
    /// adding the value to cache and returning the added value.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="add"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    public TValue RetrieveOrAdd<TValue>(string key, Func<TValue> add)
    {
        if (_cache.ContainsKey(key))
        {
            return (TValue)_cache[key];
        }

        var value = add();

        _cache[key] = value;

        return value;
    }

    /// <summary>
    /// Retrieves the value by key. When the key does not exist the default value for the type is returned.
    /// </summary>
    /// <param name="key"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    public TValue Retrieve<TValue>(string key)
    {
        if (_cache.ContainsKey(key))
        {
            return (TValue)_cache[key];
        }

        return default(TValue);
    }
}

Usando el framework DI de ASP.Net Core lo configuraremos como Scoped.

services.AddScoped<IRequestCache, RequestCache>();

Uso

public class UserService
{
    private readonly IRequestCache _cache;
    private readonly IUserRepository _userRepository;

    public UserService(IRequestCache cache, IUserRepository userRepository)
    {
        _cache = cache;
        _userRepository = userRepository;
    }

    public User RetrieveUserById(int userId)
    {
        var buildCacheKey = UserService.BuildCacheKey(userId);

        return _cache.RetrieveOrAdd(BuildCacheKey, () => { return _userRepository.RetrieveUserBy(userId); });
    }

    public void Delete(int userId)
    {
        var buildCacheKey = UserService.BuildCacheKey(userId);

        _userRepository.Delete(userId);
        _cache.Remove(BuildCacheKey(userId));
    }

    private static string BuildCacheKey(int userId)
    {
        return $"user_{userId}";
    }
}

¡Eso es todo! El caché de solicitudes ahora es inyectable en cualquier lugar donde lo necesites.

Visita el repositorio de Git y siéntete libre de probar el código.

↑ Volver arriba

Autor: Chuck Conway se especializa en ingeniería de software e IA Generativa. Conéctate con él en redes sociales: X (@chuckconway) o visítalo en YouTube.