Meziantou.Framework.Http.Caching 1.0.1

Prefix Reserved
dotnet add package Meziantou.Framework.Http.Caching --version 1.0.1
                    
NuGet\Install-Package Meziantou.Framework.Http.Caching -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Meziantou.Framework.Http.Caching" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Meziantou.Framework.Http.Caching" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Meziantou.Framework.Http.Caching" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Meziantou.Framework.Http.Caching --version 1.0.1
                    
#r "nuget: Meziantou.Framework.Http.Caching, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Meziantou.Framework.Http.Caching@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Meziantou.Framework.Http.Caching&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Meziantou.Framework.Http.Caching&version=1.0.1
                    
Install as a Cake Tool

Meziantou.Framework.Http.Caching

An HTTP caching implementation for HttpClient that follows RFC 7234 (HTTP Caching) and RFC 8246 (Immutable directive) specifications.

What is it?

Meziantou.Framework.Http.Caching provides a CachingDelegateHandler that intercepts HTTP requests and caches responses according to standard HTTP caching rules. It automatically handles cache storage, validation, freshness calculation, and cache invalidation, reducing network traffic and improving application performance.

Features

  • RFC 7234 Compliant: Fully implements HTTP caching specifications
  • RFC 8246 Immutable: Supports the immutable directive to prevent unnecessary revalidation
  • Cache-Control Directives: Supports max-age, no-cache, no-store, must-revalidate, max-stale, min-fresh, only-if-cached
  • Conditional Requests: Automatic validation using ETag and Last-Modified headers
  • Vary Support: Caches multiple variants based on request headers (e.g., Accept-Language, Accept-Encoding)
  • Cache Invalidation: Automatically invalidates cache entries on unsafe methods (POST, PUT, DELETE, PATCH)
  • Pragma Support: Handles Pragma: no-cache for HTTP/1.0 backward compatibility
  • Thread-Safe: Designed for concurrent access across multiple threads
  • Testable: Supports custom TimeProvider for deterministic testing

Installation

dotnet add package Meziantou.Framework.Http.Caching

Usage

Basic Usage

using Meziantou.Framework.Http;

// Create the caching handler
var cachingHandler = new CachingDelegateHandler();

// Create HttpClient with the caching handler
using var httpClient = new HttpClient(cachingHandler);

// Make requests - responses will be cached automatically
var response1 = await httpClient.GetAsync("https://api.example.com/data");
var response2 = await httpClient.GetAsync("https://api.example.com/data"); // Served from cache

With Custom Inner Handler

using Meziantou.Framework.Http;

var innerHandler = new HttpClientHandler();
var cachingHandler = new CachingDelegateHandler(innerHandler);

using var httpClient = new HttpClient(cachingHandler);
var response = await httpClient.GetAsync("https://api.example.com/data");

With Dependency Injection

services.AddHttpClient("MyApi")
    .AddHttpMessageHandler<CachingDelegateHandler>();

// Register the handler
services.AddTransient<CachingDelegateHandler>();

With Custom TimeProvider (for testing)

using Microsoft.Extensions.Time.Testing;

var fakeTimeProvider = new FakeTimeProvider();
var cachingHandler = new CachingDelegateHandler(fakeTimeProvider);

using var httpClient = new HttpClient(cachingHandler);

// Make a request
var response1 = await httpClient.GetAsync("https://api.example.com/data");

// Advance time
fakeTimeProvider.Advance(TimeSpan.FromMinutes(5));

// Make another request (cache freshness is evaluated with the fake time)
var response2 = await httpClient.GetAsync("https://api.example.com/data");

How It Works

Caching Behavior

The handler follows these rules when processing requests:

  1. Only GET and HEAD requests are cached (per RFC 7234 Section 4)
  2. Checks request directives like no-store, no-cache, max-age, min-fresh, max-stale, only-if-cached
  3. Evaluates cache freshness using Cache-Control: max-age and Expires headers
  4. Validates stale entries using conditional requests with If-None-Match (ETag) or If-Modified-Since
  5. Handles 304 Not Modified responses by updating and serving the cached entry
  6. Stores new responses according to caching rules

Cache Invalidation

Unsafe HTTP methods (POST, PUT, DELETE, PATCH) automatically invalidate cache entries for:

  • The request URI
  • URIs in Location header
  • URIs in Content-Location header

Vary Header Support

The cache differentiates responses based on headers specified in the Vary response header:

// Server responds with:
// Cache-Control: max-age=3600
// Vary: Accept-Language

var request1 = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data");
request1.Headers.Add("Accept-Language", "en-US");
var response1 = await httpClient.SendAsync(request1); // Caches with Accept-Language=en-US

var request2 = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data");
request2.Headers.Add("Accept-Language", "fr-FR");
var response2 = await httpClient.SendAsync(request2); // Fetches new response and caches separately

Immutable Directive (RFC 8246)

The immutable directive indicates that the response body will not change over time. When a response has Cache-Control: immutable and is still fresh, the cache will NOT perform conditional revalidation even if the client sends Cache-Control: no-cache:

// Server responds with:
// Cache-Control: max-age=31536000, immutable
// ETag: "abc123"

// First request fetches from origin
var response1 = await httpClient.GetAsync("https://cdn.example.com/script.js");

// Second request with no-cache still uses cached version (because immutable + fresh)
var request2 = new HttpRequestMessage(HttpMethod.Get, "https://cdn.example.com/script.js");
request2.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
var response2 = await httpClient.SendAsync(request2); // Served from cache, no revalidation

This is particularly useful for versioned resources (e.g., /assets/script.v123.js) that never change once published.

Supported HTTP Headers

Request Headers

Header Directive Description
Cache-Control no-store Prevents caching the response
no-cache Forces validation before using cached response (unless response is immutable and fresh)
max-age=<seconds> Maximum age for cached response
min-fresh=<seconds> Requires response to be fresh for at least this duration
max-stale[=<seconds>] Accepts stale responses (optionally up to specified staleness)
only-if-cached Returns cached response or 504 Gateway Timeout
Pragma no-cache HTTP/1.0 compatibility (equivalent to Cache-Control: no-cache)
If-None-Match <etag> Used for conditional validation
If-Modified-Since <date> Used for conditional validation

Response Headers

Header Directive Description
Cache-Control max-age=<seconds> How long the response is fresh
no-cache Requires validation before reuse
no-store Must not be cached
must-revalidate Must validate when stale
immutable Response will never change; prevents conditional revalidation when fresh (RFC 8246)
Expires <date> Expiration date (fallback if max-age not present)
ETag <value> Entity tag for conditional requests
Last-Modified <date> Last modification date for conditional requests
Vary <headers> Headers that affect response variant
Age <seconds> Age of cached response (added when serving from cache)

Thread Safety

The CachingDelegateHandler is thread-safe and can be used concurrently across multiple threads. The internal cache implementation ensures that concurrent requests to the same URL are properly coordinated.

Examples

Force Revalidation

var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data");
request.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
var response = await httpClient.SendAsync(request);
// Note: If the response has Cache-Control: immutable and is fresh, it will still be served from cache

Accept Stale Responses

var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data");
request.Headers.CacheControl = new CacheControlHeaderValue 
{ 
    MaxStale = true,
    MaxStaleLimit = TimeSpan.FromMinutes(5) // Accept up to 5 minutes stale
};
var response = await httpClient.SendAsync(request);

Only Use Cache

var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data");
request.Headers.CacheControl = new CacheControlHeaderValue { OnlyIfCached = true };
var response = await httpClient.SendAsync(request);
// Returns cached response or 504 Gateway Timeout

Testing

The library includes comprehensive tests demonstrating various caching scenarios. See the test project for examples of:

  • Cache-Control directive handling
  • Conditional request validation
  • Vary header support
  • Cache invalidation
  • Thread safety
  • URL handling
  • Immutable directive behavior

License

This project is part of the Meziantou.Framework library.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 80 1/18/2026
1.0.0 86 1/4/2026