25. HTTP Response Caching

  1. 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