Marvin.Cache.Headers 6.0.0-rc

This is a prerelease version of Marvin.Cache.Headers.
There is a newer version of this package available.
See the version list below for details.
Install-Package Marvin.Cache.Headers -Version 6.0.0-rc
dotnet add package Marvin.Cache.Headers --version 6.0.0-rc
<PackageReference Include="Marvin.Cache.Headers" Version="6.0.0-rc" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Marvin.Cache.Headers --version 6.0.0-rc
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: Marvin.Cache.Headers, 6.0.0-rc"
#r directive can be used in F# Interactive, C# scripting and .NET Interactive. Copy this into the interactive tool or source code of the script to reference the package.
// Install Marvin.Cache.Headers as a Cake Addin
#addin nuget:?package=Marvin.Cache.Headers&version=6.0.0-rc&prerelease

// Install Marvin.Cache.Headers as a Cake Tool
#tool nuget:?package=Marvin.Cache.Headers&version=6.0.0-rc&prerelease
The NuGet Team does not provide support for this client. Please contact its maintainers for support.

Http Cache Headers Middleware for ASP.NET Core

ASP.NET Core middleware that adds HttpCache headers to responses (Cache-Control, Expires, ETag, Last-Modified), and implements cache expiration & validation models. It can be used to ensure caches correctly cache responses and/or to implement concurrency for REST-based APIs using ETags.

The middleware itself does not store responses. Looking at this description, this middleware handles the "backend"-part: it generates the correct cache-related headers, and ensures a cache can check for expiration (304 Not Modified) & preconditions (412 Precondition Failed) (often used for concurrency checks).

It can be used together with a shared cache, a private cache or both. For production scenarios the best approach is to use this middleware to generate the ETags, combined with a cache server or CDN to inspect those tags and effectively cache the responses. In the sample, the Microsoft.AspNetCore.ResponseCaching cache store is used to cache the responses.

NuGet version

Installation (NuGet)

Install-Package Marvin.Cache.Headers


First, register the services with ASP.NET Core's dependency injection container (in the ConfigureServices method on the Startup class)


Then, add the middleware to the request pipeline. Starting with version 6.0, the middleware MUST be added between UseRouting() and UseEndpoints().




Configuring options

The middleware allows customization of how headers are generated. The AddHttpCacheHeaders() method has overloads for configuring options related to expiration, validation or both.

For example, this code will set the max-age directive to 600 seconds, and will add the must-revalidate directive.

    (expirationModelOptions) =>
        expirationModelOptions.MaxAge = 600;
    (validationModelOptions) =>
        validationModelOptions.MustRevalidate = true;

Action (Resource) and Controller-level Header Configuration

For anything but the simplest of cases having one global cache policy isn't sufficient: configuration at level of each resource (action/controller) is required. For those cases, use the HttpCacheExpiration and/or HttpCacheValidation attributes at action or controller level.

[HttpCacheExpiration(CacheLocation = CacheLocation.Public, MaxAge = 99999)]
[HttpCacheValidation(MustRevalidate = true)]
public IEnumerable<string> Get()
    return new[] { "value1", "value2" };

Both override the global options. Action-level configuration overrides controller-level configuration.

Marking for Invalidation (v5 onwards)

Cache invalidation essentially means wiping a response from the cache because you know it isn't the correct version anymore. Caches often partially automate this (a response can be invalidated when it becomes stale, for example) and/or expose an API to manually invalidate items.

The same goes for the cache headers middleware. Sometimes resource manipulation has an effect on related resources. Take a list of employees as an example. If a PUT statement is sent to one employees resource that one employees will get a new Etag, but the employeess resource doesn’t automatically change. If the employee you just updated is one of the employees in the returned employees when fetching the employees resource, the employees resource is out of date. Same goes for deleting or creating an employee: that, too, might have an effect on the employees resource.

To support this scenario the cache headers middleware allows marking an item for invalidation. When doing that, the related item will be removed from the internal store, meaning that for subsequent requests a stored item will not be found.

To use this, inject an IValidatorValueInvalidator and call MarkForInvalidation on it, passing through the key(s) of the item(s) you want to be removed. You can additionally inject an IStoreKeyAccessor, which contains methods that make it easy to find one or more keys from (part of) a URI.


The middleware is very extensible. If you have a look at the AddHttpCacheHeaders method you'll notice it allows injecting custom implementations of IValidatorValueStore, IStoreKeyGenerator, IETagGenerator and/or IDateParser (via actions).


A validator value store stores validator values. A validator value is used by the cache validation model when checking if a cached item is still valid. It contains ETag and LastModified properties. The default IValidatorValueStore implementation (InMemoryValidatorValueStore) is an in-memory store that stores items in a ConcurrentDictionary<string, ValidatorValue>.

/// <summary>
/// Contract for a store for validator values.  Each item is stored with a <see cref="StoreKey" /> as key```
/// and a <see cref="ValidatorValue" /> as value (consisting of an ETag and Last-Modified date).   
/// </summary>
public interface IValidatorValueStore
    /// <summary>
    /// Get a value from the store.
    /// </summary>
    /// <param name="key">The <see cref="StoreKey"/> of the value to get.</param>
    /// <returns></returns>
    Task<ValidatorValue> GetAsync(StoreKey key);
    /// <summary>
    /// Set a value in the store.
    /// </summary>
    /// <param name="key">The <see cref="StoreKey"/> of the value to store.</param>
    /// <param name="validatorValue">The <see cref="ValidatorValue"/> to store.</param>
    /// <returns></returns>
    Task SetAsync(StoreKey key, ValidatorValue validatorValue);


The StoreKey, as used by the IValidatorValueStore as key, can be customized as well. To do so, implement the IStoreKeyGenerator interface. The default implementation (DefaultStoreKeyGenerator) generates a key from the request path, request query string and request header values (taking VaryBy into account). Through StoreKeyContext you can access all applicable values that can be useful for generating such a key.

/// <summary>
/// Contract for a key generator, used to generate a <see cref="StoreKey" /> ```
/// </summary>
public interface IStoreKeyGenerator
    /// <summary>
    /// Generate a key for storing a <see cref="ValidatorValue"/> in a <see cref="IValidatorValueStore"/>.
    /// </summary>
    /// <param name="context">The <see cref="StoreKeyContext"/>.</param>         
    /// <returns></returns>
    Task<StoreKey> GenerateStoreKey(
        StoreKeyContext context);


You can inject an IETagGenerator-implementing class to modify how ETags are generated (ETags are part of a ValidatorValue). The default implementation (DefaultStrongETagGenerator) generates strong Etags from the request key + response body (MD5 hash from combined bytes).

/// <summary>
/// Contract for an E-Tag Generator, used to generate the unique weak or strong E-Tags for cache items
/// </summary>
public interface IETagGenerator
    Task<ETag> GenerateETag(
        StoreKey storeKey,
        string responseBodyContent);


You can inject an ILastModifiedInjector-implementing class to modify how LastModified values are provided. The default implementation (DefaultLastModifiedInjector) injects the current UTC.

/// <summary>
/// Contract for a LastModifiedInjector, which can be used to inject custom last modified dates for resources
/// of which you know when they were last modified (eg: a DB timestamp, custom logic, ...)
/// </summary>
public interface ILastModifiedInjector
    Task<DateTimeOffset> CalculateLastModified(
        ResourceContext context);


Through IDateParser you can inject a custom date parser in case you want to override the default way dates are stringified. The default implementation (DefaultDateParser) uses the RFC1123 pattern (

/// <summary>
/// Contract for a date parser, used to parse Last-Modified, Expires, If-Modified-Since and If-Unmodified-Since headers.
/// </summary>
public interface IDateParser
    Task<string> LastModifiedToString(DateTimeOffset lastModified);

    Task<string> ExpiresToString(DateTimeOffset lastModified);

    Task<DateTimeOffset?> IfModifiedSinceToDateTimeOffset(string ifModifiedSince);

    Task<DateTimeOffset?> IfUnmodifiedSinceToDateTimeOffset(string ifUnmodifiedSince);


An IValidatorValueInvalidator-implenting class is responsible for marking items for invalidation.

/// <summary>
/// Contract for the <see cref="ValidatorValueInvalidator" />
/// </summary>
public interface IValidatorValueInvalidator
    /// <summary>
    /// Get the list of <see cref="StoreKey" /> of items marked for invalidation
    /// </summary>
    List<StoreKey> KeysMarkedForInvalidation { get; }

    /// <summary>
    /// Mark an item stored with a <see cref="StoreKey" /> for invalidation
    /// </summary>
    /// <param name="storeKey">The <see cref="StoreKey" /></param>
    /// <returns></returns>
    Task MarkForInvalidation(StoreKey storeKey);

    /// <summary>
    /// Mark a set of items for invlidation by their collection of <see cref="StoreKey" /> 
    /// </summary>
    /// <param name="storeKeys">The collection of <see cref="StoreKey" /></param>
    /// <returns></returns>
    Task MarkForInvalidation(IEnumerable<StoreKey> storeKeys);


The IStoreKeyAccessor contains helper methods for getting keys from parts of a URI. Override this if you're not storing items with their default keys.

/// <summary>
/// Contract for finding (a) <see cref="StoreKey" />(s)
/// </summary>    
public interface IStoreKeyAccessor
    /// <summary>
    /// Find a  <see cref="StoreKey" /> by part of the key
    /// </summary>
    /// <param name="valueToMatch">The value to match as part of the key</param>
    /// <returns></returns>
    Task<IEnumerable<StoreKey>> FindByKeyPart(string valueToMatch);

    /// <summary>
    /// Find a  <see cref="StoreKey" /> of which the current resource path is part of the key
    /// </summary>
    /// <returns></returns>
    Task<IEnumerable<StoreKey>> FindByCurrentResourcePath();
  • .NETCoreApp 3.1

    • No dependencies.
  • net5.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Marvin.Cache.Headers:

Package Downloads

Package Description


Package Description

GitHub repositories (3)

Showing the top 3 popular GitHub repositories that depend on Marvin.Cache.Headers:

Repository Stars
Fake JSON Server is a Fake REST API that can be used as a Back End for prototyping or as a template for a CRUD Back End.
Fully functioning sample application accompanying my Implementing Advanced RESTful Concerns with ASP.NET Core 3 course
Version Downloads Last updated
6.0.0 1,272 12/6/2021
6.0.0-rc 487 10/25/2021
5.0.1 63,861 4/16/2020
5.0.0 2,875 2/25/2020
5.0.0-beta 351 1/21/2020
4.1.0 15,793 11/21/2019
4.0.0 10,798 8/19/2019
3.2.0 3,288 7/3/2019
3.1.0 21,674 2/19/2019
3.0.0 10,367 9/27/2018
3.0.0-beta 629 8/8/2018
2.0.1 5,233 7/25/2018
2.0.0 649 7/24/2018
1.2.0 4,709 4/30/2018
1.1.0 24,184 11/7/2017
1.0.0 3,931 2/27/2017
1.0.0-beta 704 1/16/2017

Stability improvements, a new feature that allows ignoring endpoints, bug fixes, multi-target for Core 3.1 & NET50.  Attention: this is a major release which includes breaking changes - check the milestone over at Github for more info.