Skip to content

Cache Keys

Aghogho Bernard edited this page May 12, 2026 · 2 revisions

Cache Keys

CacheWeave assembles cache keys from multiple segments in a deterministic order. Understanding this lets you predict exactly what key will be used for any request.

Key Assembly Order

{baseKey}{sep}{version?}{sep}{contextSegment?}{sep}{routeParams?}{sep}{queryParams?}{sep}{bodyHash?}
Segment Source Example
Base key [CacheWeave("my-key")] or derived from controller/action products:list
Version CacheWeaveOptions.KeyVersion v2
Context IKeyContextProvider.GetContextSegmentAsync() tenant=acme
Route params Sorted route values from URL path (e.g. {id}) id=42
Query params Sorted, filtered query string page=1:pageSize=20
Body hash SHA-256 of request body (or selected fields) a3f9c2d1...

Example — Full Key

GET /api/products/42/reviews?page=2
→ products:reviews:v2:tenant=acme:id=42:page=2

Base Key

Explicit Key

[CacheWeave("products:list")]

The string "products:list" is used verbatim as the first segment.

Derived Key

[CacheWeave]  // no key argument

CacheWeave uses the ControllerActionDescriptor to derive:

{ControllerName}.{ActionName}

Examples:

Controller Action Derived Key
ProductsController GetAll Products.GetAll
MaterialCategoriesController GetById MaterialCategories.GetById
ClientMaterialsController GetStatistics ClientMaterials.GetStatistics

Version Segment

Set CacheWeaveOptions.KeyVersion to inject a version after the base key:

options.KeyVersion = "v2";
// "products:list" → "products:list:v2"

Invalidation strategy: bump the version in config/environment variables to instantly invalidate all cached entries across all instances without touching Redis.

Context Segment (Tenant / User Isolation)

Implement IKeyContextProvider to inject a per-request segment:

public class TenantKeyContextProvider : IKeyContextProvider
{
    private readonly IHttpContextAccessor _accessor;

    public TenantKeyContextProvider(IHttpContextAccessor accessor)
        => _accessor = accessor;

    public Task<string?> GetContextSegmentAsync(HttpContext context)
    {
        var tenantId = context.User.FindFirst("tenant_id")?.Value;
        return Task.FromResult(tenantId is not null ? $"tenant={tenantId}" : null);
    }
}

Register it:

services.AddHttpContextAccessor();
services.AddSingleton<IKeyContextProvider, TenantKeyContextProvider>();

Result:

products:list:v1:tenant=acme:page=1

Route Params

When IncludeRouteParams = true (default), route values from the URL path are sorted alphabetically and appended to the cache key. Framework-owned route values (controller, action, page, area) are always excluded automatically.

GET /api/products/42
→ key: products:id=42

Why This Matters

Without route params in the key, endpoints like [HttpGet("{id}")] would produce the same cache key for /products/1 and /products/2, returning incorrect cached responses.

Excluding Specific Route Params

Strip route values that don't affect the response:

[HttpGet("{id}/{version}")]
[CacheWeave("products", ExcludeRouteParams = ["version"])]
public Task<IActionResult> GetById(int id, string version) { ... }
// GET /products/42/draft → key: "products:id=42" (version excluded)

Disabling Route Params

[CacheWeave("products:stats", IncludeRouteParams = false)]

Route Params vs Query Params

Route params and query params are independent and stored in different places on the HttpContext:

Route params Query params
URL location In the path: /products/42 After ?: /products?id=42
ASP.NET source HttpContext.Request.RouteValues HttpContext.Request.Query
CacheWeave toggle IncludeRouteParams IncludeQueryParams
Exclusion list ExcludeRouteParams ExcludeParams

Both can appear in the same key:

GET /api/products/42/reviews?page=2&sort=newest
→ key: products:reviews:id=42:page=2:sort=newest
                        ^^^^^ ^^^^^^^^^^^^^^^^^^^^
                    route param   query params

Query Params

When IncludeQueryParams = true (default), all query parameters are sorted alphabetically and appended:

GET /api/products?pageSize=20&page=1&category=steel
→ key: products:list:category=steel:page=1:pageSize=20

Excluding Noisy Params

Strip tracking IDs, debug flags, or correlation headers that don't affect the response:

[CacheWeave("products:list",
    IncludeQueryParams = true,
    ExcludeParams = ["trackingId", "correlationId", "debug"])]

Disabling Query Params

[CacheWeave("products:stats", IncludeQueryParams = false)]

Body Hash (POST Endpoints)

For POST endpoints where the request body determines the response, append a SHA-256 hash:

[HttpPost("search")]
[CacheWeave("products:search", HashBody = true)]
public async Task<IActionResult> Search([FromBody] SearchRequest request) { ... }

Key: products:search:a3f9c2d1e4b7...

Selective Field Hashing

If the body contains noise fields (request IDs, timestamps) that don't affect the result, hash only the stable fields:

[CacheWeave("products:search",
    HashBody = true,
    HashBodyFields = ["term", "categoryId", "filters"])]

Only the listed fields are extracted and hashed — the rest are ignored.

Key Separator

Change the separator globally:

options.KeySeparator = ".";
// "products.list.v1.page=1"

The default ":" is recommended for Redis as it enables namespace-based key browsing in RedisInsight and similar tools.

Clone this wiki locally