Skip to content
46 changes: 46 additions & 0 deletions aspnetcore/performance/caching/hybrid.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,52 @@ The stateless overload of `GetOrCreateAsync` is recommended for most scenarios.

:::code language="csharp" source="~/performance/caching/hybrid/samples/9.x/HCMinimal/Program.cs" id="snippet_getorcreate" highlight="5-12":::

## Cache key guidance

The `key` passed to `GetOrCreateAsync` must uniquely identify the data being cached:

* In terms of the identifier values used to retrieve that data from its source.
* In terms of other data cached in the application.

Both types of uniqueness are usually ensured by using string concatenation to make a single key string composed of different parts concatenated into one string. For example:

```c#
cache.GetOrCreateAsync($"/orders/{region}/{orderId}", ...);
```

or

```c#
cache.GetOrCreateAsync($"user_prefs_{userId}", ...);
```

It's the caller's responsibility to ensure that a key scheme is valid and can't cause data to become confused.

We recommend that you not use external user input in the cache key. For example, don't use raw `string` values from a UI as part of a cache key. Such keys could allow malicious access attempts, or could be used in a denial-of-service attack by saturating your cache with data having meaningless keys generated from random strings. In the preceding valid examples, the *order* data and *user preference* data are clearly distinct:

* `orderid` and `userId` are internally generated identifiers.
* `region` might be an enum or string from a predefined list of known regions.

There is no significance placed on tokens such as `/` or `_`. The entire key value is treated as an opaque identifying string. In this case, you could omit the `/` and `_` with no
change to the way the cache functions, but a delimiter is usually used to avoid ambiguity - for example `$"order{customerId}{orderId}"` could cause confusion betwen:

- `customerId` 42 with `orderId` 123
- `customerId` 421 with `orderId` 23

(both of which would generate the cache key `order42123`)

This guidance applies equally to any `string`-based cache API, such as `HybridCache`, `IDistributedCache`, and `IMemoryCache`.

The inline interpolated string syntax (`$"..."` above, directly inside the `GetOrCreateAsync` call) is recommended when using `HybridCache`, as this allows for planned future
improvements that bypass the need to allocate a `string` for the key in many scenarios.

Additional key restrictions:

- keys may restricted to valid lengths; for example, the default `HybridCache` implementation (via `AddHybridCache(...)`) restricts keys to 1024 characters by default, (configurable via `HybridCacheOptions.MaximumKeyLength`) with longer keys bypassing the cache mechanisms to prevent saturation.
- keys must be valid unicode sequences; if invalid unicode sequences are passed, the behaviour is undefined.
- when using an out-of-process secondary cache (`IDistributedCache`), the specific backend implementation may impose additional restrictions - as a hypothetical example (no such actual
backend is known), a particular backend might use case-insensitive key logic; the default `HybridCache` (via `AddHybridCache(...)`) will detect this scenario to prevent confusion attacks, however it may still result in conflicting keys becoming overwritten/evicted sooner than expected.

### The alternative `GetOrCreateAsync` overload

The alternative overload might reduce some overhead from [captured variables](/dotnet/csharp/language-reference/operators/lambda-expressions#capture-of-outer-variables-and-variable-scope-in-lambda-expressions) and per-instance callbacks, but at the expense of more complex code. For most scenarios the performance increase doesn't outweigh the code complexity. Here's an example that uses the alternative overload:
Expand Down