Skip to content

Articles

Implémentation de la mise en cache des requêtes dans ASP.Net Core

8 juillet 2019 • 5 min de lecture

Implémentation de la mise en cache des requêtes dans ASP.Net Core

À un moment donné du développement d’une application, généralement assez tôt, vous réalisez que l’application est lente. Après quelques recherches, le coupable est la récupération inutile des mêmes données, et une ampoule s’allume, et vous pensez : “J’ai besoin de mise en cache.”

La mise en cache est un modèle inestimable pour éliminer les appels redondants à une base de données ou à une API tierce. Microsoft fournit IMemoryCache pour la mise en cache basée sur le temps, cependant parfois la mise en cache basée sur le temps n’est pas ce dont vous avez besoin. Dans cet article, nous examinons la mise en cache à portée de requête et comment elle peut nous être bénéfique.

Qu’est-ce que la mise en cache de requête ? La mise en cache de requête est un mécanisme pour mettre en cache des données pendant la durée de vie d’une requête web. Dans dot-net, nous avons eu cette capacité dans une certaine mesure avec la collection HttpContext.Items, cependant, HttpContext n’est pas connu pour son injectabilité.

La mise en cache à portée de requête présente quelques avantages : Premièrement, elle élimine la préoccupation des données obsolètes. Dans la plupart des scénarios, une requête s’exécute en moins d’une seconde, ce qui n’est généralement pas assez long pour que les données deviennent obsolètes. Et deuxièmement, l’expiration n’est pas une préoccupation car les données meurent lorsque la requête se termine.

Par défaut, Asp.Net Core n’a pas de mise en cache injectable. Comme mentionné précédemment, HttpContext.Items est une option, mais ce n’est pas une solution élégante.

Heureusement pour nous, ASP.Net Core nous donne les outils pour créer une implémentation de mise en cache de requête injectable en utilisant le framework d’injection de dépendance (DI) intégré.

Le framework DI intégré a trois durées de vie pour les dépendances : Singleton, Scoped, et Transient. Singleton est pour la durée de vie de l’application, Scoped est pour la durée de vie de la requête et Transient est une nouvelle instance à chaque requête.

J’ai créé une interface modélée d’après l’interface IMemoryCache pour garder les choses cohérentes.

Interface

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);
}

Implémentation

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);
    }
}

En utilisant le framework DI d’ASP.Net Core, nous allons le configurer comme Scoped.

services.AddScoped<IRequestCache, RequestCache>();

Utilisation

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}";
    }
}

C’est tout ! La mise en cache de requête est maintenant injectable partout où vous en avez besoin.

Visitez le dépôt Git et n’hésitez pas à tester le code.

Auteur : Chuck Conway se spécialise dans l’ingénierie logicielle et l’IA générative. Connectez-vous avec lui sur les réseaux sociaux : X (@chuckconway) ou visitez-le sur YouTube.

↑ Retour en haut

Vous pourriez aussi aimer