// Đăng ký
builder.Services.AddMemoryCache();
// Sử dụng
public class ProductService
{
private readonly IMemoryCache _cache;
private readonly AppDbContext _context;
public ProductService(IMemoryCache cache, AppDbContext context)
{
_cache = cache;
_context = context;
}
public async Task<List<Product>> GetProductsAsync()
{
// TryGetValue - Kiểm tra cache
if (!_cache.TryGetValue("products", out List<Product> products))
{
// Load từ database
products = await _context.Products.ToListAsync();
// Set cache với options
var options = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(10))
.SetAbsoluteExpiration(TimeSpan.FromHours(1))
.SetPriority(CacheItemPriority.Normal)
.SetSize(products.Count);
_cache.Set("products", products, options);
}
return products;
}
public async Task<Product> GetProductByIdAsync(int id)
{
var key = $"product_{id}";
return await _cache.GetOrCreateAsync(key, async entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(5);
return await _context.Products.FindAsync(id);
});
}
}
var options = new MemoryCacheEntryOptions()
// Thời gian cache không được access trước khi expire
.SetSlidingExpiration(TimeSpan.FromMinutes(10))
// Thời gian cache tồn tại tuyệt đối
.SetAbsoluteExpiration(TimeSpan.FromHours(1))
// Kết hợp cả hai
.SetAbsoluteExpirationRelativeToNow(TimeSpan.FromHours(1))
// Callback khi cache bị remove
.RegisterPostEvictionCallback((key, value, reason, state) =>
{
Console.WriteLine($"Cache '{key}' removed: {reason}");
});
dotnet add package StackExchange.Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
options.InstanceName = "MyApp:";
});
public class RedisCacheService
{
private readonly IDistributedCache _cache;
public RedisCacheService(IDistributedCache cache)
{
_cache = cache;
}
// Set
public async Task SetAsync<T>(string key, T value)
{
var json = JsonSerializer.Serialize(value);
var bytes = Encoding.UTF8.GetBytes(json);
await _cache.SetAsync(key, bytes, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
SlidingExpiration = TimeSpan.FromMinutes(10)
});
}
// Get
public async Task<T> GetAsync<T>(string key)
{
var bytes = await _cache.GetAsync(key);
if (bytes == null) return default;
var json = Encoding.UTF8.GetString(bytes);
return JsonSerializer.Deserialize<T>(json);
}
// Remove
public async Task RemoveAsync(string key)
{
await _cache.RemoveAsync(key);
}
}
// Program.cs
builder.Services.AddResponseCaching();
var app = builder.Build();
app.UseResponseCaching();
[ApiController]
[Route("api/[controller]")]
[ResponseCache(Duration = 60, VaryByHeader = "Accept")]
public class ProductsController : ControllerBase
{
[HttpGet]
[ResponseCache(Duration = 120)]
public IActionResult GetProducts()
{
return Ok(new { data = "cached" });
}
}
[HttpGet]
public IActionResult GetProducts()
{
Response.Headers.CacheControl = "public, max-age=60";
Response.Headers.Vary = "Accept-Encoding";
return Ok();
}
┌─────────────────────────────────────────────────────────────────┐
│ CACHE-ASIDE PATTERN │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Request → Cache │
│ ↓ │
│ 2. Cache hit? ──No──→ Database → Cache → Response │
│ ↓ │
│ Yes │
│ ↓ │
│ 3. Response (from cache) │
│ │
└─────────────────────────────────────────────────────────────────┘
public async Task<Product> GetProductAsync(int id)
{
var key = $"product_{id}";
// 1. Check cache
var cached = await _cache.GetAsync<Product>(key);
if (cached != null)
return cached;
// 2. Load from database
var product = await _context.Products.FindAsync(id);
if (product != null)
{
// 3. Store in cache
await _cache.SetAsync(key, product, TimeSpan.FromMinutes(10));
}
return product;
}
public async Task UpdateProductAsync(Product product)
{
// Update database
_context.Products.Update(product);
await _context.SaveChangesAsync();
// Invalidate cache
await _cache.RemoveAsync($"product_{product.Id}");
}