Articles
Implémentation de la mise en cache des requêtes dans ASP.Net Core
8 juillet 2019 • 5 min de lecture
À 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 lumière 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 avec portée de requête et comment elle peut nous être bénéfique.
Qu’est-ce que la mise en cache des requêtes ? La mise en cache des requêtes est un mécanisme pour mettre en cache les données pendant la durée de vie d’une requête web. En dot-net, nous avons eu cette capacité à un certain degré avec la collection HttpContext.Items, cependant, HttpContext n’est pas connu pour son injectabilité.
La mise en cache avec portée de requête présente quelques avantages : Premièrement, elle élimine le problème des données obsolètes. Dans la plupart des scénarios, une requête s’exécute en moins d’une seconde, ce qui généralement n’est pas assez long pour que les données deviennent obsolètes. Deuxièmement, l’expiration n’est pas une préoccupation car les données disparaissent à la fin de la requête.
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 des requêtes injectable en utilisant le framework d’injection de dépendances (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élisée d’après l’interface IMemoryCache pour maintenir la cohérence.
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 le configurerons 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 des requêtes est maintenant injectable n’importe où vous en avez besoin.
Visitez le référentiel Git et n’hésitez pas à tester le code.
Auteur : Chuck Conway est un ingénieur IA avec près de 30 ans d’expérience en génie logiciel. Il construit des systèmes IA pratiques — pipelines de contenu, agents d’infrastructure et outils qui résolvent des problèmes réels — et partage ce qu’il apprend en chemin. Connectez-vous avec lui sur les réseaux sociaux : X (@chuckconway) ou visitez-le sur YouTube et sur SubStack.