Skip to content

Configuring cache entries

Override GetCacheEntryOptions to control expiration, priority, size and eviction callbacks; the default is a five-minute sliding expiration.

2 min read

Expiration is controlled by one overridable method on ApplicationCache<TRequest, TResponse>:

protected virtual MemoryCacheEntryOptions GetCacheEntryOptions();

Its default implementation is:

protected virtual MemoryCacheEntryOptions GetCacheEntryOptions() =>
	new()
	{
		SlidingExpiration = TimeSpan.FromMinutes(5),
	};

Overriding it

GetUserCache.cs
[CacheFor<GetUser>]
public sealed partial class GetUserCache
{
	protected override string TransformKey(GetUser.Query request) =>
		$"GetUser({request.UserId})";

	protected override MemoryCacheEntryOptions GetCacheEntryOptions() =>
		new()
		{
			AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
			Priority = CacheItemPriority.High,
		};
}

MemoryCacheEntryOptions lives in Microsoft.Extensions.Caching.Memory. Everything the memory cache understands is available: AbsoluteExpiration, AbsoluteExpirationRelativeToNow, SlidingExpiration, Priority, Size, ExpirationTokens and PostEvictionCallbacks.

To cache forever, return an empty MemoryCacheEntryOptions — that replaces the default sliding expiration with no expiration at all:

protected override MemoryCacheEntryOptions GetCacheEntryOptions() => new();

When it is called

The method is invoked once per cache key, at the moment the entry is first created — which happens on the first GetValue, SetValue, RemoveValue or TransformValue for that request. It takes no parameters, so you cannot vary the policy per request: one cache class means one entry policy for all of its keys. Split the handler into separate handlers with separate cache classes if you need different lifetimes.

Because the method is called only at creation time, options are not re-evaluated later. A sliding window, on the other hand, is refreshed by every subsequent cache operation, since each one performs a lookup against IMemoryCache.

What expiring actually does

The value stored under the key is an internal wrapper holding the shared execution slot and the cached response, not the response itself. When the entry expires or is evicted, that wrapper goes with it, and the next GetValue creates a fresh one and re-executes the handler.

Sharing a policy across caches

You cannot introduce a shared base class between your cache and ApplicationCache<,>: the generated partial declaration already fixes the base type. Share a policy with a plain helper instead:

CachePolicies.cs
internal static class CachePolicies
{
	public static MemoryCacheEntryOptions ShortLived() =>
		new()
		{
			SlidingExpiration = TimeSpan.FromMinutes(1),
			Priority = CacheItemPriority.Low,
		};
}
GetUserCache.cs
protected override MemoryCacheEntryOptions GetCacheEntryOptions() =>
	CachePolicies.ShortLived();

Next: testing caches.