Beiträge
Implementierung von Request-Caching in ASP.Net Core
8. Juli 2019 • 4 Min. Lesezeit

Irgendwann in der Entwicklung einer Anwendung, normalerweise ziemlich früh, stellt man fest, dass die Anwendung langsam ist. Nach einiger Recherche ist der Übeltäter das unnötige Abrufen derselben Daten, und es macht Klick, und man denkt: “Ich brauche Caching.”
Caching ist ein unschätzbares Muster zur Eliminierung redundanter Aufrufe an eine Datenbank oder eine Drittanbieter-API. Microsoft stellt IMemoryCache
für zeitbasiertes Caching bereit, jedoch ist zeitbasiertes Caching manchmal nicht das, was man braucht. In diesem Artikel betrachten wir Request-Scoped-Caching und wie es uns nutzen kann.
Was ist Request-Caching? Request-Caching ist ein Mechanismus zum Zwischenspeichern von Daten für die Lebensdauer einer Web-Anfrage. In .NET hatten wir diese Fähigkeit in gewissem Umfang mit der HttpContext.Items-Sammlung, jedoch ist HttpContext
nicht für seine Injizierbarkeit bekannt.
Request-Scoped-Caching hat einige Vorteile: Erstens eliminiert es die Sorge um veraltete Daten. In den meisten Szenarien wird eine Anfrage in weniger als einer Sekunde ausgeführt, was normalerweise nicht lang genug ist, damit Daten veralten. Und zweitens ist das Ablaufen kein Problem, da die Daten sterben, wenn die Anfrage endet.
Standardmäßig hat ASP.Net Core kein injizierbares 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 eingebaute Dependency Injection (DI) Framework verwenden.
Das eingebaute 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 ein Interface erstellt, das nach dem IMemoryCache
-Interface modelliert ist, um die Dinge konsistent zu halten.
Interface
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 ASP.Net Cores DI-Framework verdrahten 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 Sie es benötigen.
Besuchen Sie das Git-Repository und probieren Sie gerne den Code aus.
Autor: Chuck Conway ist spezialisiert auf Software-Engineering und Generative KI. Verbinden Sie sich mit ihm in den sozialen Medien: X (@chuckconway) oder besuchen Sie ihn auf YouTube.