Implementando Estratégias de Cache em ASP.NET Core 3.1

Este guia explora a utilização do mecanismo de cache em memória nativo do ASP.NET Core 3.1, demonstrando diferentes abordagens como expiração absoluta, deslizante e dependência de arquivo.

Configuração Inicial

Para habiltiar o cache em memória, registre o serviço no método ConfigureServices da sua classe Startup:

services.AddMemoryCache();

Helper de Cache Personalizado

É recomendável criar uma classe auxiliar para gerenciar as operações de cache. Abaixo, um exemplo de como encapsular a lógica de cache:


using Microsoft.Extensions.Caching.Memory;
using System;
using System.IO;
using Microsoft.Extensions.FileProviders.Physical;

public static class CacheHelper
{
   private static readonly IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());
   private static readonly object cacheLock = new object();

   private static IMemoryCache GetCacheInstance()
   {
       if (memoryCache == null)
       {
           lock (cacheLock)
           {
               if (memoryCache == null)
               {
                   // Em um cenário real, pode ser necessário injetar MemoryCacheOptions
                   // via DI para configuração mais avançada.
                   return new MemoryCache(new MemoryCacheOptions());
               }
           }
       }
       return memoryCache;
   }

   /// <summary>
   /// Define um item no cache com expiração absoluta ou deslizante.
   /// </summary>
   /// <typeparam name="TValue">Tipo do valor a ser cacheado.</typeparam>
   /// <param name="cacheKey">A chave do cache.</param>
   /// <param name="value">O valor a ser armazenado.</param>
   /// <param name="absoluteMinutes">Tempo em minutos para expiração absoluta. Se 0, usa expiração deslizante.</param>
   /// <param name="slidingMinutes">Tempo em minutos para expiração deslizante. Usado se absoluteMinutes for 0.</param>
   public static void SetCacheItem<TValue>(string cacheKey, TValue value, int absoluteMinutes = 5, int slidingMinutes = 0)
   {
       using (var entry = GetCacheInstance().CreateEntry(cacheKey))
       {
           entry.Value = value;

           if (absoluteMinutes > 0)
           {
               entry.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(absoluteMinutes);
           }
           else if (slidingMinutes > 0)
           {
               entry.SlidingExpiration = TimeSpan.FromMinutes(slidingMinutes);
           }
       }
   }

   /// <summary>
   /// Define um item no cache com dependência de arquivo.
   /// </summary>
   /// <typeparam name="TValue">Tipo do valor a ser cacheado.</typeparam>
   /// <param name="cacheKey">A chave do cache.</param>
   /// <param name="value">O valor a ser armazenado.</param>
   /// <param name="filePath">O caminho do arquivo para monitoramento.</param>
   public static void SetCacheItemWithFileDependency<TValue>(string cacheKey, TValue value, string filePath)
   {
       if (!File.Exists(filePath))
       {
           // Garante que o arquivo exista para que o PollingFileChangeToken funcione.
           // Em um cenário real, você pode querer lidar com isso de forma mais robusta.
           File.Create(filePath).Dispose();
       }

       var fileInfo = new FileInfo(filePath);
       using (var entry = GetCacheInstance().CreateEntry(cacheKey))
       {
           entry.Value = value;
           entry.AddExpirationToken(new PollingFileChangeToken(fileInfo, TimeSpan.FromSeconds(1))); // Verifica a cada segundo
       }
   }

   /// <summary>
   /// Obtém um item do cache.
   /// </summary>
   /// <typeparam name="TValue">Tipo do valor esperado.</typeparam>
   /// <param name="cacheKey">A chave do cache.</param>
   /// <returns>O valor em cache ou o valor padrão do tipo se não encontrado.</returns>
   public static TValue GetCacheItem<TValue>(string cacheKey)
   {
       if (GetCacheInstance().TryGetValue(cacheKey, out TValue cachedValue))
       {
           return cachedValue;
       }
       return default;
   }

   /// <summary>
   /// Remove um item do cache.
   /// </summary>
   /// <param name="cacheKey">A chave do cache.</param>
   public static void RemoveCacheItem(string cacheKey)
   {
       GetCacheInstance().Remove(cacheKey);
   }
}
   

Testando Cache com Dependência de Arquivo

Abaixo está um exemplo de como usar o cache com dependência de arquivo em um endpoint de API:


using Microsoft.AspNetCore.Mvc;
using System;

[ApiController]
[Route("api/[controller]")]
public class CacheController : ControllerBase
{
   [HttpGet("file-dependency")]
   public IActionResult GetWithFileDependency()
   {
       const string cacheKey = "fileDataCache";
       string cachedData = CacheHelper.GetCacheItem<string>(cacheKey);

       if (string.IsNullOrEmpty(cachedData))
       {
           string dataToCache = $"Dados carregados em: {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
           // Certifique-se de que o arquivo 'cache_monitor.txt' exista no diretório base da aplicação.
           // Você pode criá-lo manualmente ou via código na inicialização.
           string monitorFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache_monitor.txt");
           CacheHelper.SetCacheItemWithFileDependency(cacheKey, dataToCache, monitorFilePath);
           cachedData = dataToCache;
       }

       return Ok(new { Message = "Dados do cache com dependência de arquivo", Data = cachedData });
   }

   [HttpGet("absolute-cache")]
   public IActionResult GetWithAbsoluteCache()
   {
       const string cacheKey = "absoluteTimeCache";
       string cachedTime = CacheHelper.GetCacheItem<string>(cacheKey);

       if (string.IsNullOrEmpty(cachedTime))
       {
           cachedTime = $"Tempo absoluto: {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
           CacheHelper.SetCacheItem(cacheKey, cachedTime, absoluteMinutes: 1); // Cache válido por 1 minuto
       }

       return Ok(new { Message = "Dados do cache com expiração absoluta", Data = cachedTime });
   }

    [HttpGet("sliding-cache")]
   public IActionResult GetWithSlidingCache()
   {
       const string cacheKey = "slidingTimeCache";
       string cachedTime = CacheHelper.GetCacheItem<string>(cacheKey);

       if (string.IsNullOrEmpty(cachedTime))
       {
           cachedTime = $"Tempo deslizante: {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
           CacheHelper.SetCacheItem(cacheKey, cachedTime, absoluteMinutes: 0, slidingMinutes: 2); // Cache válido por 2 minutos sem atividade
       }

       return Ok(new { Message = "Dados do cache com expiração deslizante", Data = cachedTime });
   }
}
   

Comportamento da Dependência de Arquivo

  • Ao acessar o endpoint /api/cache/file-dependency, os dados são carregados e armazenados em cache.
  • Se você solicitar o mesmo endpoint várias vezes sem modificar o arquivo cache_monitor.txt, os dados em cache serão retornados.
  • Ao modificar o conteúdo do arquivo cache_monitor.txt e, em seguida, solicitar o endpoint novamente, o cache será invalidado e os dados serão recarregados, refletindo a modificação.

Exemplo de Estrutura de Configuração e Modelo

Para cenários onde você carrega configurações de arquivos JSON e deseja cacheá-las:


using Microsoft.Extensions.Configuration;
using System.IO;

public class AppConfigLoader
{
   private const string ConfigCacheKey = "jwtConfigCacheKey";

   public static JwtConfigModel LoadJwtConfiguration()
   {
       JwtConfigModel config = CacheHelper.GetCacheItem<JwtConfigModel>(ConfigCacheKey);

       if (config == null)
       {
           var configuration = new ConfigurationBuilder()
               .SetBasePath(Directory.GetCurrentDirectory())
               .AddJsonFile("configs/appsettings.json") // Assumindo um arquivo de configuração
               .Build();

           config = configuration.GetSection("JwtSettings").Get<JwtConfigModel>();

           // Cacheia a configuração carregada, talvez com dependência de arquivo se appsettings.json for monitorado.
           string configFilePath = Path.Combine(Directory.GetCurrentDirectory(), "configs", "appsettings.json");
           CacheHelper.SetCacheItemWithFileDependency(ConfigCacheKey, config, configFilePath);
       }
       return config;
   }
}

public class JwtConfigModel
{
   public string SecretKey { get; set; }
   public string Issuer { get; set; }
   public string Audience { get; set; }
}
   

Tags: ASP.NET Core memory cache cache caching dotnet

Publicado em 6-15 06:08 por Thomas