21. HTTP Response Caching

HTTP Response Caching

About this chapter

Leverage HTTP response caching headers to enable browsers and CDNs to cache responses, reducing server load for public data and read-only endpoints.

  • Response caching middleware: Installing and configuring ASP.NET Core caching
  • Cache-Control headers: Using ResponseCache attribute to control caching behavior
  • Duration and expiration: Setting appropriate cache durations for different endpoints
  • Public vs private caching: Differentiating between shared caches and client-only caching
  • Query parameter variation: Creating separate cache entries for different parameters
  • Cache prevention: Disabling caching for state-mutating operations

Learning outcomes:

  • Add response caching middleware to the pipeline
  • Use ResponseCache attribute to control caching
  • Set appropriate cache durations for different endpoints
  • Understand Cache-Control header options
  • Cache GET endpoints appropriately
  • Prevent caching on POST/PUT/PATCH/DELETE operations

25.1 HTTP Response Caching

Response caching uses HTTP headers to tell browsers and proxies to cache responses. This reduces server load for cacheable endpoints.

dotnet add package Microsoft.AspNetCore.ResponseCaching
// Program.cs
builder.Services.AddResponseCaching();

var app = builder.Build();

app.UseResponseCaching();  // Must be before MapControllers
app.MapControllers();

25.2 Cache-Control Headers

Tell clients how long to cache responses:

[HttpGet("{id}")]
[ResponseCache(Duration = 300, Location = ResponseCacheLocation.Any)]
public async Task<ActionResult<PlatformReadDto>> GetPlatformById(int id)
{
    // Sends: Cache-Control: public, max-age=300
    // Browsers and intermediate caches will cache for 5 minutes
    var platform = await _repository.GetPlatformByIdAsync(id);
    return Ok(_mapper.Map<PlatformReadDto>(platform));
}

[HttpGet]
[ResponseCache(Duration = 600, VaryByQueryKeys = new[] { "pageIndex", "pageSize" })]
public async Task<ActionResult> GetPlatforms(int pageIndex = 1, int pageSize = 10)
{
    // Different cache entries for different page parameters
    // pageIndex=1&pageSize=10 cached separately from pageIndex=2&pageSize=10
    return Ok();
}

[HttpPost]
[ResponseCache(NoStore = true)]
public async Task<ActionResult> CreatePlatform(PlatformMutateDto dto)
{
    // Sends: Cache-Control: no-store
    // Never cache POST requests (they mutate state)
    return CreatedAtRoute(nameof(GetPlatformById), new { id = 1 }, dto);
}

[HttpGet("profile")]
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
public async Task<ActionResult> GetUserProfile()
{
    // Sends: Cache-Control: private, max-age=60
    // Only browsers cache this, not intermediate caches (CDNs, proxies)
    // Used for user-specific data
    return Ok();
}

Caching Rules:

  • GET endpoints: Can be cached (idempotent)
  • POST/PUT/PATCH/DELETE: Never cache (mutate data)
  • Public data: Location = ResponseCacheLocation.Any (CDNs, proxies, browsers)
  • User-specific data: Location = ResponseCacheLocation.Client (browsers only)

Note on GraphQL HTTP caching as a criticism