SetVaryByQuery Can Make ASP.NET Core Output Cache Serve Wrong Pages
The article explains how calling SetVaryByQuery in an ASP.NET Core output cache policy replaces the default vary-by-all-query-keys behavior. A policy that names only sort stops varying by page, so different pages can receive the same cached body. The author reproduces the bug with a small test and shows the fix.
Key points
- SetVaryByQuery("sort") replaces the default "*" vary-by rule, so page no longer forms part of the cache key.
- The fix is to pass every query parameter the endpoint reads, for example SetVaryByQuery("page", "sort").
- Named policies from AddPolicy build on the default policy, so the bug only appears once you narrow the vary-by list.
- Update the vary-by list whenever you add a new query filter to the endpoint.
- excludeDefaultPolicy: true disables caching entirely, so it is not a fix for this bug.
A paging bug in an ASP.NET Core product list came from a misreading of SetVaryByQuery in output cache policies. The second page returned the first page's cached body, even though the default policy had correctly varied by every query parameter. The author found that a named policy calling SetVaryByQuery with only sort silently dropped page from the cache key.
The method replaces the default vary-by list instead of adding to it. The author built a small self-hosted test with three endpoints to confirm the behavior, and the results matched the bug. The fix is to list every query parameter the handler depends on, such as page and sort. The article also warns that excludeDefaultPolicy turns caching off entirely, so it is not a clean-slate option for this problem.
Real-world use
- Speeding up a product list endpoint with paging and sorting using ASP.NET Core output caching.
- Defining named output cache policies for an API that accepts several query filters.
- Writing a small regression test that checks cache entries are separated per page and sort value.
Why it matters
If the vary-by list is incomplete, output caching can serve one user's page to another without any error. Every named policy should list all query parameters the endpoint depends on.