Skip to content

Creating a cache

Declare a cache class, implement TransformKey, and register the generated caches with the service collection.

3 min read

A cache is a partial class marked with [CacheFor<THandler>] that supplies one method: TransformKey, which turns a request into the string used as the cache key. The source generator adds the base class and the constructor.

GetValue.cs
[Handler]
public sealed partial class GetValue
{
	public sealed record Query(int Value);
	public sealed record Response(int Value);

	private ValueTask<Response> HandleAsync(
		Query query,
		CancellationToken _
	) => ValueTask.FromResult(new Response(query.Value));
}
GetValueCache.cs
[CacheFor<GetValue>]
public sealed partial class GetValueCache
{
	protected override string TransformKey(GetValue.Query request) =>
		$"GetValue(query: {request.Value})";
}

GetValueCache now derives from ApplicationCache<GetValue.Query, GetValue.Response> — the generator infers both type arguments from the handler’s HandleAsync signature, so you never write them yourself. That is also why TransformKey is an override in a class with no visible base type.

Requirements

RequirementEnforced by
The cache class is declared partialCompiler (CS0260)
The cache class is not nested in another typeIC0001
The target type carries [Handler]IC0002
The target’s handle method returns ValueTask<T>IC0003

Bare ValueTask command handlers cannot be cached: there is no response to store, and IC0003 reports it.

Registering the generated caches

In your Program.cs, add a call to services.AddXxxCaches(), where Xxx is the application identifier. By default this is the assembly name with . and spaces removed:

  • For a project named Web, it will be services.AddWebCaches()
  • For a project named Application.Web, it will be services.AddApplicationWebCaches()

The name can be overridden with [assembly: ImmediateAssemblyIdentifier("SomeIdentifier")] — see The assembly identifier.

Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMemoryCache();
builder.Services.AddWebBehaviors();
builder.Services.AddWebHandlers();
builder.Services.AddWebCaches();

Consuming the cache

Each cache class is registered under its own concrete type, not under ApplicationCache<,> or any interface. Inject the class directly:

UsersController.cs
public sealed class UsersController(GetValueCache cache)
{
	public async Task<GetValue.Response> Get(int value, CancellationToken token) =>
		await cache.GetValue(new GetValue.Query(value), token);
}

Cache classes are registered as Singletons, so never inject a scoped service such as a DbContext into your cache class — see How it works. Scoped dependencies belong on the handler.

Next: reading and writing cached data.