Describe the bug
OutputCachePolicyBuilder.With(...) wraps the complete built policy, including DefaultPolicy, in PredicatePolicy. PredicatePolicy evaluates the predicate separately during the request, cache-serve, and response phases.
If the predicate is true when the request enters the output cache middleware but becomes false while the endpoint executes, mutations made during CacheRequestAsync remain active while ServeResponseAsync is skipped. In particular, DefaultPolicy can enable cache storage during the request phase without getting an opportunity to reject the resulting response. The response can then be stored despite having a Set-Cookie header, an authenticated user, or a non-200 status code.
Expected behavior
A With(...) requirement should have one applicability result for the lifetime of a request. The result established during CacheRequestAsync should also control the cache-serve and response phases.
Steps to reproduce
Run this minimal app:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOutputCache();
var app = builder.Build();
var executions = 0;
app.UseOutputCache();
app.MapGet("/", (HttpContext context) =>
{
context.Items["EndpointExecuted"] = true;
context.Response.Headers["X-Execution"] = (++executions).ToString();
context.Response.Cookies.Append("repro", "value");
return Results.Redirect("/target");
}).CacheOutput(policy => policy.With(context =>
!context.HttpContext.Items.ContainsKey("EndpointExecuted")));
app.Run();
Request / twice without following the redirect:
curl -i http://localhost:5000/
curl -i http://localhost:5000/
Both responses are 302, contain Set-Cookie: repro=value and X-Execution: 1, and the second response has an Age header. This shows that the second response was served from the cache without executing the endpoint again.
The predicate is initially true, so DefaultPolicy.CacheRequestAsync enables storage. The endpoint then changes the predicate result to false, causing PredicatePolicy to skip DefaultPolicy.ServeResponseAsync and its response exclusions.
Describe the bug
OutputCachePolicyBuilder.With(...)wraps the complete built policy, includingDefaultPolicy, inPredicatePolicy.PredicatePolicyevaluates the predicate separately during the request, cache-serve, and response phases.If the predicate is
truewhen the request enters the output cache middleware but becomesfalsewhile the endpoint executes, mutations made duringCacheRequestAsyncremain active whileServeResponseAsyncis skipped. In particular,DefaultPolicycan enable cache storage during the request phase without getting an opportunity to reject the resulting response. The response can then be stored despite having aSet-Cookieheader, an authenticated user, or a non-200 status code.Expected behavior
A
With(...)requirement should have one applicability result for the lifetime of a request. The result established duringCacheRequestAsyncshould also control the cache-serve and response phases.Steps to reproduce
Run this minimal app:
Request
/twice without following the redirect:Both responses are
302, containSet-Cookie: repro=valueandX-Execution: 1, and the second response has anAgeheader. This shows that the second response was served from the cache without executing the endpoint again.The predicate is initially
true, soDefaultPolicy.CacheRequestAsyncenables storage. The endpoint then changes the predicate result tofalse, causingPredicatePolicyto skipDefaultPolicy.ServeResponseAsyncand its response exclusions.