
På et tidspunkt i en applikasjons utvikling, vanligvis ganske tidlig, innser du at applikasjonen er treg. Etter litt forskning viser det seg at årsaken er unødvendig henting av samme data, og en lyspære går opp, og du tenker: “Jeg trenger litt caching.”
Caching er et uvurderlig mønster for å eliminere overflødige kall til en database eller et tredjeparts API. Microsoft tilbyr IMemoryCache
for tidsbasert caching, men noen ganger er ikke tidsbasert caching det du trenger. I denne artikkelen ser vi på forespørselsscopet caching og hvordan det kan være til nytte for oss.
Hva er forespørselscaching? Forespørselscaching er en mekanisme for å cache data for levetiden til en webforespørsel. I dot-net har vi hatt denne muligheten i en viss kapasitet med HttpContext.Items-samlingen, men HttpContext
er ikke kjent for sin injiserbarhet.
Forespørselsscopet caching har noen fordeler: For det første eliminerer det bekymringen for utdaterte data. I de fleste scenarioer utføres en forespørsel på mindre enn et sekund, som vanligvis ikke er lenge nok til at data blir utdaterte. Og for det andre er utløp ikke en bekymring fordi dataene dør når forespørselen avsluttes.
Ut av boksen har ikke Asp.Net Core injiserbar caching. Som nevnt tidligere er HttpContext.Items
et alternativ, men det er ikke en elegant løsning.
Heldigvis for oss gir ASP.Net Core oss verktøyene for å lage en injiserbar forespørselscaching-implementering ved å bruke det innebygde dependency injection (DI) rammeverket.
Det innebygde DI-rammeverket har tre levetider for avhengigheter: Singleton
, Scoped
, og Transient
. Singleton
er for applikasjonens levetid, Scoped er for forespørselens levetid og Transient er en ny instans ved hver forespørsel.
Jeg har laget et grensesnitt modellert etter IMemoryCache
-grensesnittet for å holde ting konsistente.
Grensesnitt
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);
}
Implementering
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);
}
}
Ved å bruke ASP.Net Cores DI-rammeverk kobler vi det opp som Scoped
.
services.AddScoped<IRequestCache, RequestCache>();
Bruk
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}";
}
}
Det er det! Forespørselscaching er nå injiserbar hvor som helst du trenger det.
Besøk Git-repositoriet og ta gjerne koden for en prøvetur.
Forfatter: Chuck Conway spesialiserer seg på programvareutvikling og Generativ AI. Koble til ham på sosiale medier: X (@chuckconway) eller besøk ham på YouTube.