Skip to content

Commit ac032e6

Browse files
authored
Merge pull request #34644 from mgravell/hybridcache_secreview
HybridCache - additional guidance on cache key composition
2 parents 683b981 + 706798e commit ac032e6

File tree

1 file changed

+44
-0
lines changed

1 file changed

+44
-0
lines changed

aspnetcore/performance/caching/hybrid.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,50 @@ The stateless overload of `GetOrCreateAsync` is recommended for most scenarios.
5252

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

55+
## Cache key guidance
56+
57+
The `key` passed to `GetOrCreateAsync` must uniquely identify the data being cached:
58+
59+
* In terms of the identifier values used to retrieve that data from its source.
60+
* In terms of other data cached in the application.
61+
62+
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:
63+
64+
```c#
65+
cache.GetOrCreateAsync($"/orders/{region}/{orderId}", ...);
66+
```
67+
68+
or
69+
70+
```c#
71+
cache.GetOrCreateAsync($"user_prefs_{userId}", ...);
72+
```
73+
74+
It's the caller's responsibility to ensure that a key scheme is valid and can't cause data to become confused.
75+
76+
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:
77+
78+
* `orderid` and `userId` are internally generated identifiers.
79+
* `region` might be an enum or string from a predefined list of known regions.
80+
81+
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
82+
change to the way the cache functions, but a delimiter is usually used to avoid ambiguity - for example `$"order{customerId}{orderId}"` could cause confusion between:
83+
84+
- `customerId` 42 with `orderId` 123
85+
- `customerId` 421 with `orderId` 23
86+
87+
(both of which would generate the cache key `order42123`)
88+
89+
This guidance applies equally to any `string`-based cache API, such as `HybridCache`, `IDistributedCache`, and `IMemoryCache`.
90+
91+
Notice that the inline interpolated string syntax (`$"..."` in the preceding examples of valid keys) is directly inside the `GetOrCreateAsync` call. This syntax is recommended when using `HybridCache`, as it allows for planned future improvements that bypass the need to allocate a `string` for the key in many scenarios.
92+
93+
### Additional key considerations
94+
95+
* Keys can be restricted to valid maximum lengths. For example, the default `HybridCache` implementation (via `AddHybridCache(...)`) restricts keys to 1024 characters by default. That number is configurable via `HybridCacheOptions.MaximumKeyLength`, with longer keys bypassing the cache mechanisms to prevent saturation.
96+
* Keys must be valid Unicode sequences. If invalid Unicode sequences are passed, the behavior is undefined.
97+
* When using an out-of-process secondary cache such as `IDistributedCache`, the specific backend implementation may impose additional restrictions. As a hypothetical example, a particular backend might use case-insensitive key logic. The default `HybridCache` (via `AddHybridCache(...)`) detects this scenario to prevent confusion attacks, however it may still result in conflicting keys becoming overwritten or evicted sooner than expected.
98+
5599
### The alternative `GetOrCreateAsync` overload
56100

57101
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:

0 commit comments

Comments
 (0)