System.InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.OutputCaching.IOutputCacheStore' while attempting to activate 'Microsoft.AspNetCore.OutputCaching.OutputCacheMiddleware'. — you added the output-cache middleware without registering the services it is built from.

Output caching in ASP.NET Core is three pieces, and .CacheOutput() is the smallest one: a marker on the endpoint. The work is done by app.UseOutputCache(), and that middleware needs builder.Services.AddOutputCache(). Wire only the middleware and the app throws at startup; wire only the marker and nothing is cached and nothing complains. Add all three — or, if all you want is the browser to keep the response, send a Cache-Control header and skip the whole feature.

The error

A minimal app with UseOutputCache() and no AddOutputCache(), started from the command line:

crit: Microsoft.AspNetCore.Hosting.Diagnostics[6]
      Application startup exception
      System.InvalidOperationException: Unable to resolve service for type
      'Microsoft.AspNetCore.OutputCaching.IOutputCacheStore' while attempting to activate
      'Microsoft.AspNetCore.OutputCaching.OutputCacheMiddleware'.
         at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(...)
         at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.ReflectionMiddlewareBinder.CreateMiddleware(...)
         at Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()

Why it happens

UseOutputCache() inserts OutputCacheMiddleware into the pipeline. When the pipeline is built, the middleware's constructor asks the container for an IOutputCacheStore (and the options and policies that go with it). AddOutputCache() is what registers those. Without it, dependency injection fails while constructing the middleware, and because the pipeline is built as the host starts, the exception lands before the first request.

The quieter failure is the one that fooled us. .CacheOutput() on its own only attaches metadata to the endpoint. Measured on a scratch app: with no middleware the endpoint answers 200, every time, with no caching and no Cache-Control header. It looks like it works because there is nothing to see.

The fix

builder.Services.AddOutputCache();          // 1. services
/* ... */
var app = builder.Build();
app.UseOutputCache();                       // 2. middleware, before the endpoints
app.MapGet("/api/pocket/visits/{code}/qr.png", /* ... */)
   .CacheOutput(p => p.Expire(TimeSpan.FromDays(1)));   // 3. the marker

The alternative is what ClinicLive shipped. A QR image for a given code never changes, so the browser may keep it; no server memory, no middleware:

// src/ClinicLive/Api/PocketEndpoints.cs
http.Response.Headers.CacheControl = "public, max-age=86400";
return Results.File(TicketQr.Png(code.Trim().ToUpperInvariant()), "image/png");

Where it bit us

Season three, Part 8 (tag pocket-08 in the repo). The AI's first draft of the ticket-QR endpoint ended in .CacheOutput() as if it were a header. It was swapped for Cache-Control before the app ran, which is why the log says "before it could throw" — and, as the measurement above shows, on its own it would not have thrown at all; it would have cached nothing and said so to nobody. Two lessons: a one-line API can have two lines of setup behind it, and the cheapest cache is the one in the visitor's browser.

Frequently asked

Does CacheOutput() work without UseOutputCache()?
No. CacheOutput() only adds metadata to the endpoint. Without the OutputCacheMiddleware in the pipeline the endpoint runs normally on every request, with no caching and no error, so the omission is easy to miss.
What is the difference between output caching and a Cache-Control header?
Output caching stores the rendered response on the server and replays it to later requests. Cache-Control tells the browser or a CDN how long it may reuse its own copy. For a response that never changes for a given URL, the header alone is often enough.
Where does UseOutputCache() go in Program.cs?
After routing and CORS, and before the endpoints are mapped. In a minimal-API app that usually means right after builder.Build() and before the MapGet calls, with AddOutputCache() registered on builder.Services earlier.

More decoded errors in the Fixes category; the API this came from starts at From Prompt to Pocket, Part 1.