Usando ActionFilterAttribute com Memory Cache C#

 Usando ActionFilterAttribute com Memory Cache C#

Usando ActionFilterAttribute com Memory Cache C#



Salve Dev, me chamo Jorel Magatti e sou Desenvolvedor c# .Net, aqui trago um artigo sobre a utilização de ActionFilterAttribute com MemoryCache.
primeiramente caso queira conhecer melhor sobre o uso basico de uma classe erdada da ActionFilterAttribute, recomendo a leitura do artigo abaixo:

A classe MemoryCache é uma implementação concreta da classe abstrata ObjectCache. Ela representa o tipo que implementa um cache em memória. A classe MemoryCache fornece várias opções para configurar o cache, como políticas de expiração, expiração deslizante e níveis de prioridade. É um provedor de cache leve, ideal para armazenar pequenas quantidades de dados.

Segue abaixo uma implementação básica de uma classe herdada da ActionFilterAttribute , feita para armazenar dados em cache na memória, neste exemplo mostro também como enviar argumentos para um método ActionFilterAttribute e como buscar services de sua injeção de dependências:

public class MemCacheAttribute : ActionFilterAttribute
{
private readonly int _timeout;
private string _key { get; set; }
private string _keyMem { get; set; }

public MemCacheAttribute(int timeout, string key)
{
this._timeout = timeout;
this._key = key;
}

public override void OnActionExecuting(ActionExecutingContext context)
{
IMemoryCache _cache = context.HttpContext.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;

_keyMem = GenerateHash(string.Join("_", parametros.Values) + "_" + _key);

if (_cache.TryGetValue(_keyMem, out var cachedResult))
{
context.Result = (IActionResult)cachedResult;
return;
}

base.OnActionExecuting(context);
}

public override void OnActionExecuted(ActionExecutedContext context)
{
IMemoryCache _cache = context.HttpContext.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;

if (!_cache.TryGetValue(_keyMem, out var cachedResult))
{
var resultado = context.Result;
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(_timeout))
.SetAbsoluteExpiration(TimeSpan.FromSeconds(_timeout));
_cache.Set(_keyMem, resultado, cacheEntryOptions);
}
}

private string GenerateHash(string keyValue)
{
byte[] hashBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(keyValue));
string hashString = BitConverter.ToString(hashBytes).Replace("-", "");
return hashString;
}
}

Em nossa classe MemCacheAttribute temos um acoplamento de dados com parâmetros globais alimentados pelo construtor da mesma:

    private readonly int _timeout;
private string _key { get; set; }
private string _keyMem { get; set; }
    public MemCacheAttribute(int timeout, string key)
{
this._timeout = timeout;
this._key = key;
}

Desta forma conseguimos receber argumentos em nosso parâmetro de filtro para receber nossas configurações básicas que usaremos no MemoryCache.

Para o comportamento principal da minha classe MemCacheAttribute, estamos sobrescrevendo dois métodos da herança ActionFilterAttributeOnActionExecuting OnActionExecuted, segue abaixo:

OnActionExecuting

OnActionExecuting -> é chamado antes da execução de uma ação do controlador. Ele é um método virtual que pode ser substituído em uma classe derivada para executar lógica personalizada antes que a ação seja executada

public override void OnActionExecuting(ActionExecutingContext context)

Antes da execução da nossa action na controller, precisamos validar se existe algum cache relacionada com a nossa chamada, para isso temos a regra abaixo:

IMemoryCache _cache = context.HttpContext.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;

_keyMem = GenerateHash(string.Join("_", parametros.Values) + "_" + _key);

if (_cache.TryGetValue(_keyMem, out var cachedResult))
{
context.Result = (IActionResult)cachedResult;
return;
}

base.OnActionExecuting(context);

Primeiro chamamos em nossa injeção de dependência uma referencia ao MemoryCache:

IMemoryCache _cache = context.HttpContext.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;

Após isso geramos uma chave Hash usando o método GenerateHash baseado com parametros enviados no request mais a informação enviada na chamada do filtro com a variavel _key:

//gerando chave para busca e criação dos dados em cache
//convertemos todos os parametros em uma string separa por _, mais a chave padrão
_keyMem = GenerateHash(string.Join("_", parametros.Values) + "_" + _key);

//método para geração de string de Hash com dados de uma string comum
private string GenerateHash(string keyValue)
{
byte[] hashBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(keyValue));
string hashString = BitConverter.ToString(hashBytes).Replace("-", "");
return hashString;
}

E agora por fim validamos se existe algum dado em cache em memória com aquela chave :

if (_cache.TryGetValue(_keyMem, out var cachedResult))
{
// caso exista dados em cache ele atribui no retorno da action pelo context
// e da um return para filnalizar o processo
context.Result = (IActionResult)cachedResult;
return;
}

base.OnActionExecuting(context);

OnActionExecuted

OnActionExecuted -> é chamado depois que uma ação do controlador é executada. Ele é um método virtual que pode ser substituído em uma classe derivada para executar lógica personalizada após a execução da ação.

public override void OnActionExecuted(ActionExecutedContext context)

No caso do OnActionExecuted, conseguimos acessar os dados de retorno da nossa Action na controller, com isso temos o retorno que a API estaria retornando em json para o cliente, e com estes dados vamos gerar os dados no cache em memória, para isso temos a regra abaixo:

//buscamos o cache na injeção de dependencia 
IMemoryCache _cache = context.HttpContext.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;

//validamos se não existe aquele cache
if (!_cache.TryGetValue(_keyMem, out var cachedResult))
{
//caso não existe setamos estes dados no cache com MemoryCache
//baseado nos parametros _timeout e _keyMem gerado no OnActionExecuting
var resultado = context.Result;
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(_timeout))
.SetAbsoluteExpiration(TimeSpan.FromSeconds(_timeout));
_cache.Set(_keyMem, resultado, cacheEntryOptions);
}

Desta forma sentamos o cache na memória e retornamos os dados salvos para o cliente final.

Para usarmos nossa classe MemCacheAttribute em um Actions na controller basta:

//tempo de expiração do seu cache em segundos
//Nome da sua chave para incrementar com parametros
[MemCache(30, "Nome_da_sua_chave_para_incrementar_com_parametros")]

dentro da controller ficaria assim:

[HttpGet]
[Route("v1/GetUser/{id:int}")]
[MemCache(30, "_User")]
public async Task<IActionResult> GetUser(int id)
{
// seu código
}

Gostei muito desta implementação, porque podemos usar este parâmetro de filtro em vários métodos diferentes e não precisamos adicionar regra de criação e validação de cache na controller, com isso removemos duplicidade no código, talvez precise melhorar a geração da chave do cache pelo fato das possibilidades de parâmetros presentes na chamada da Action como objetos compostos entre outros.

Espero que este artigo tenha lhe ajudado dev 🤘 bom estudos 🖖.

Recentes