Implementing a Generic Cache Manager in C#

In large-scale web applications, caching is a fundamental technique alongside asynchronous and parallel processnig. For externally exposed APIs, implementing caching is crucial to prevent repeated database queries that can lead to server instability.

Below is a generic cache manager interface that defines the core operations for a caching system.

public interface ICacheProvider
{
    /// <summary>
    /// Retrieves a cached item by its key.
    /// </summary>
    T Retrieve<T>(string cacheKey);

    /// <summary>
    /// Stores an item in the cache with a specified expiration time.
    /// </summary>
    void Store(string cacheKey, object item, int expirationMinutes);

    /// <summary>
    /// Checks if an item exists in the cache.
    /// </summary>
    bool Exists(string cacheKey);

    /// <summary>
    /// Removes an item from the cache by its key.
    /// </summary>
    void Evict(string cacheKey);

    /// <summary>
    /// Removes all items whose keys match a given pattern.
    /// </summary>
    void EvictByPattern(string keyPattern);

    /// <summary>
    /// Clears all items from the cache.
    /// </summary>
    void Purge();
}

A concrete implementation using the .NET MemoryCache is provided below.

public class MemoryCacheProvider : ICacheProvider
{
    private ObjectCache CacheStore => MemoryCache.Default;

    public T Retrieve<T>(string cacheKey)
    {
        return (T)CacheStore[cacheKey];
    }

    public void Store(string cacheKey, object item, int expirationMinutes)
    {
        if (item == null) return;

        var policy = new CacheItemPolicy
        {
            AbsoluteExpiration = DateTime.Now.AddMinutes(expirationMinutes)
        };
        CacheStore.Add(new CacheItem(cacheKey, item), policy);
    }

    public bool Exists(string cacheKey)
    {
        return CacheStore.Contains(cacheKey);
    }

    public void Evict(string cacheKey)
    {
        CacheStore.Remove(cacheKey);
    }

    public void EvictByPattern(string keyPattern)
    {
        var regex = new Regex(keyPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        var keysToRemove = CacheStore
            .Where(entry => regex.IsMatch(entry.Key))
            .Select(entry => entry.Key)
            .ToList();

        foreach (var key in keysToRemove)
        {
            Evict(key);
        }
    }

    public void Purge()
    {
        foreach (var entry in CacheStore)
        {
            Evict(entry.Key);
        }
    }
}

To enhance functionality, extension methods can be added for conveinent cache access with automatic loading.

public static class CacheExtensions
{
    public static T GetOrCreate<T>(this ICacheProvider cache, string key, Func<T> factory)
    {
        return GetOrCreate(cache, key, 60, factory);
    }

    public static T GetOrCreate<T>(this ICacheProvider cache, string key, int cacheMinutes, Func<T> factory)
    {
        if (cache.Exists(key))
        {
            return cache.Retrieve<T>(key);
        }

        var result = factory();
        cache.Store(key, result, cacheMinutes);
        return result;
    }
}

This pattern can be used in data access methods to automatical cache results.

private List<ProjectModel> FetchProjectList()
{
    return _cacheProvider.GetOrCreate("PROJECT_LIST", 5, () =>
    {
        return _repository.GetProjects().ToList();
    });
}

Tags: C# Caching MemoryCache Design Patterns ASP.NET

Posted on Sat, 15 Aug 2026 16:10:17 +0000 by thisisnuts123