dotnet / dotnet/aspnetcore

Add ResponseCache (CacheControl) to Minimal Api

Open
#62,143 0 comments 7 reactions 0 assignees View on GitHub
area-middleware
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

### Is there an existing issue for this?

- [x] I have searched the existing issues

### Is your feature request related to a problem? Please describe the problem.

In ASP.NET Core Minimal APIs, however, there is currently no built-in support for setting `Cache-Control` headers declaratively—unlike what exists in MVC via [ResponseCache] or [OutputCache].

This gap forces developers to manually set cache headers in each endpoint, which is repetitive, error-prone, and inconsistent.

### Use Case A — Private caching (only client)
**Endpoint**: GET `/api/user/ownProfile`

In this case, the response contains user-specific data. The ideal behavior is to cache the response only on the client side, not on intermediate proxies. **Server-side caching is optional but not required.**

Desired header (example):
```
Cache-Control: private, max-age=60
```

### Use Case B — Shared caching (client + reverse proxy)
**Endpoint**: GET `/api/user/publicDepartments`

This endpoint returns public, infrequently changing data. It should be cached both by clients and reverse proxies.

Desired header (example):
```
Cache-Control: public, max-age=300
```

At the moment, there's no unified way to describe and apply this caching policy declaratively in Minimal APIs. Developers are forced to do things like:
```
app.MapGet("/api/user/publicDepartments", () =>
{
context.Response.Headers["Cache-Control"] = "public,max-age=300";
return Results.Ok(...);
});
```

There are also custom solutions proposed by other community members. Some of them include custom filters, others implement their own OutputCachePolicy and etc. `But they do not provide a robust implementation, which can lead to major security issues in applications.`

This topic was partially discussed [here](https://github.com/dotnet/aspnetcore/issues/56769#issuecomment-2272336761) and [here](https://github.com/dotnet/aspnetcore/issues/49130).

### Describe the solution you'd like

Introduce a builder-based extension for Minimal APIs to declaratively configure HTTP response caching behavior, focusing solely on HTTP-compliant cache directives such as Cache-Control, Vary, ETag, and Last-Modified.

Key Goals

- Fluent API design allows for flexibility in configuration, unlike the old implementation with the ResponseCache attribute. This allow combining multiple directives in a fluent and composable way.
- Support [HTTP Caching RFC](https://datatracker.ietf.org/doc/html/rfc9111)
- No server-side response caching or storage (unlike OutputCache)

Proposal:
```
app.MapGet("/api/user/publicDepartments", () => ...)
.WithCacheControl(policy => policy
.Public()
.MaxAge(TimeSpan.FromMinutes(5))
.VaryByHeader("Accept-Encoding")
);

app.MapGet("/api/user/ownProfile", () => ...)
.WithCacheControl(policy => policy
.Private()
.MaxAge(TimeSpan.FromSeconds(60))
.NoTransform()
);

app.MapGet("/api/data/download", () => ...)
.WithCacheControl(policy => policy
.Public()
.Immutable()
.MaxAge(TimeSpan.FromDays(30))
);
```

**Rationale for Additional Features (example)**

`NoTransform`: Prevents proxies from modifying images, videos, or text.
`Immutable`: Allows aggressive client-side caching when content is versioned (e.g., fingerprinted URLs).
`SharedMaxAge`: Important when intermediaries (e.g., CDNs) should cache differently than browsers.
`MustRevalidate`: Helps enforce strict revalidation when freshness expires.

In my opinion, the topic of dividing the cache into an external one based on headers and an internal one based on internal storage requires very careful consideration.

### Additional context

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.