Posts
Implementando Cache de Requisições no ASP.Net Core
8 de julho de 2019 • 4 min de leitura
Em algum momento do desenvolvimento de uma aplicação, geralmente bem cedo, você percebe que a aplicação está lenta. Após algumas pesquisas, o culpado é estar recuperando desnecessariamente os mesmos dados, e uma luz se acende, e você pensa: “Preciso de cache.”
Cache é um padrão inestimável para eliminar chamadas redundantes a um banco de dados ou uma API de terceiros. A Microsoft fornece IMemoryCache para cache baseado em tempo, porém às vezes cache baseado em tempo não é o que você precisa. Neste artigo, analisamos cache com escopo de requisição e como isso pode nos beneficiar.
O que é cache de requisição? Cache de requisição é um mecanismo para armazenar dados durante a vida de uma requisição web. No dot-net, temos tido essa capacidade em alguma medida com a coleção HttpContext.Items, porém, HttpContext não é conhecido por sua injetabilidade.
Cache com escopo de requisição tem alguns benefícios: Primeiro, elimina a preocupação com dados obsoletos. Na maioria dos cenários, uma requisição é executada em menos de um segundo e o que tipicamente não é tempo suficiente para os dados ficarem obsoletos. E em segundo lugar, expiração não é uma preocupação porque os dados morrem quando a requisição termina.
Fora da caixa, o Asp.Net Core não possui cache injetável. Como mencionado anteriormente, HttpContext.Items é uma opção, mas não é uma solução elegante.
Felizmente para nós, o ASP.Net Core nos fornece as ferramentas para criar uma implementação de Cache de Requisição injetável usando o framework de injeção de dependência (DI) integrado.
O framework de DI integrado tem três tempos de vida para dependências: Singleton, Scoped e Transient. Singleton é para a vida da aplicação, Scoped é para a vida da requisição e Transient é uma nova instância a cada requisição.
Criei uma interface modelada após a interface IMemoryCache para manter as coisas consistentes.
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);
}
Implementação
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 o framework de DI do ASP.Net Core, vamos configurá-lo 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}";
}
}
Pronto! Cache de Requisição agora é injetável em qualquer lugar que você precise.
Visite o Repositório Git e sinta-se livre para testar o código.
Autor: Chuck Conway é um Engenheiro de IA com quase 30 anos de experiência em engenharia de software. Ele constrói sistemas de IA práticos—pipelines de conteúdo, agentes de infraestrutura e ferramentas que resolvem problemas reais—e compartilha o que está aprendendo ao longo do caminho. Conecte-se com ele nas redes sociais: X (@chuckconway) ou visite-o no YouTube e no SubStack.