Irgendwann während der Entwicklung einer Anwendung, normalerweise ziemlich früh, stellt man fest, dass die Anwendung langsam ist. Nach einiger Recherche stellt sich heraus, dass das Problem darin besteht, dass dieselben Daten unnötigerweise wiederholt abgerufen werden, und dann denkt man sich: „Ich brauche etwas Caching.”
Caching ist ein unschätzbares Muster zur Vermeidung redundanter Aufrufe an eine Datenbank oder eine Drittanbieter-API. Microsoft stellt IMemoryCache für zeitbasiertes Caching bereit, aber manchmal ist zeitbasiertes Caching nicht das, was man braucht. In diesem Artikel schauen wir uns Request-Scoped-Caching an und wie es uns nutzen kann.
Was ist Request-Caching? Request-Caching ist ein Mechanismus zum Zwischenspeichern von Daten für die Dauer einer Webanfrage. In .NET hatten wir diese Möglichkeit in gewissem Umfang mit der HttpContext.Items-Sammlung, aber HttpContext ist nicht für seine Injizierbarkeit bekannt.
Request-Scoped-Caching hat mehrere Vorteile: Erstens beseitigt es die Sorge vor veralteten Daten. In den meisten Szenarien wird eine Anfrage in weniger als einer Sekunde ausgeführt, was normalerweise nicht lange genug ist, damit Daten veralten. Und zweitens ist das Ablaufen kein Problem, da die Daten enden, wenn die Anfrage endet.
Out of the Box hat ASP.Net Core kein injiziertes Caching. Wie bereits erwähnt, ist HttpContext.Items eine Option, aber es ist keine elegante Lösung.
Glücklicherweise gibt uns ASP.Net Core die Werkzeuge, um eine injizierbare Request-Caching-Implementierung zu erstellen, indem wir das integrierte Dependency-Injection-Framework (DI) verwenden.
Das integrierte DI-Framework hat drei Lebensdauern für Abhängigkeiten: Singleton, Scoped und Transient. Singleton ist für die Lebensdauer der Anwendung, Scoped ist für die Lebensdauer der Anfrage und Transient ist eine neue Instanz bei jeder Anfrage.
Ich habe eine Schnittstelle erstellt, die nach der IMemoryCache-Schnittstelle modelliert ist, um die Konsistenz zu wahren.
Schnittstelle
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);
}
Implementierung
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);
}
}
Mit dem DI-Framework von ASP.Net Core verbinden wir es als Scoped.
services.AddScoped<IRequestCache, RequestCache>();
Verwendung
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}";
}
}
Das war’s! Request-Caching ist jetzt überall dort injizierbar, wo man es braucht.
Besuchen Sie das Git-Repository und probieren Sie den Code gerne aus.
Autor: Chuck Conway ist ein KI-Ingenieur mit fast 30 Jahren Erfahrung in der Softwareentwicklung. Er entwickelt praktische KI-Systeme – Content-Pipelines, Infrastruktur-Agenten und Tools, die echte Probleme lösen – und teilt seine Erkenntnisse unterwegs. Verbinden Sie sich mit ihm in den sozialen Medien: X (@chuckconway) oder besuchen Sie ihn auf YouTube und auf SubStack.