Skip to content

v11 update: Response compression always emits Vary: Accept-Encoding #37723

Description

@wadepickett

Target repository: dotnet/AspNetCore.Docs
Analyzed at commit: 4986136881f2bbf1879f10106467769df5a49932
Product source verified at: dotnet/aspnetcore @ 1fcd7ef305697a1888f3ede076010350ae9f4f8d
Source release note: Response compression always emits Vary: Accept-Encoding


🎯 Goal

The behavior change is already captured in the breaking-changes docset (breaking-changes/11/response-compression-always-vary.md). The gap is in the evergreen how-to: performance/response-compression.md has an "Add the Vary header" section whose text still says the middleware adds Vary "when the response is compressed" — the exact condition that changed. A reader on the how-to page gets the pre-.NET-11 story with no pointer to the new always-emit behavior. Close that with a version-scoped note plus a cross-link.

This is a behavior change on an existing middleware with no new API, so the element below names the prior behavior explicitly.


✅ Coverage status summary

Legend: ✅ already documented · ✏️ update needed · 🟣 could not determine

# Feature element from What's New Status Where
1 The behavior change itself — a dedicated breaking-change article explaining that Vary: Accept-Encoding is now always appended ✅ breaking-changes/11/response-compression-always-vary.md
2 The how-to's "Add the Vary header" section still describes the old, compression-conditional behavior ("automatically when the response is compressed") and doesn't mention the .NET 11 always-emit change ✏️ 1. Update — performance/response-compression.md L251–L255
3 Prior behavior (element 2's contrast): Vary: Accept-Encoding was added only when the middleware actually compressed the body ✏️ Same insertion — the note must state the old condition so the change is legible
4 Motivation: correct cache keys for shared caches/CDNs so a compressed variant isn't served to a client that didn't request that encoding ✏️ Same insertion (one sentence of rationale)

🔢 Version applicability

Applies to: CURRENT-ONLY
Target moniker: >= aspnetcore-11.0
Earlier versions affected: None — the always-emit behavior ships in .NET 11 (PR dotnet/aspnetcore#55092, issue dotnet/aspnetcore#48008). The existing 6.0-scoped paragraph stays correct for 6.0–10.0.

Article monikerRange Moniker state
performance/response-compression.md (no front-matter monikerRange; article is fully zoned inline) State D — the insertion point is inside a :::moniker range=">= aspnetcore-6.0" zone spanning L227–L269. Adding an 11.0-only note requires the four-directive split: close >= aspnetcore-6.0, open >= aspnetcore-11.0, add the note, close it, reopen >= aspnetcore-6.0. The pre-existing 6.0 paragraph is left intact because it's accurate for 6.0–10.0.

The insertion point is not inside any # [Tab](#tab/...) group, so no tab-nesting hazard applies.


📋 Coverage gap summary

A developer configuring response compression reads performance/response-compression.md, reaches "Add the Vary header," and is told the middleware adds Vary "automatically when the response is compressed." In .NET 11 that's no longer the whole story: the middleware appends Vary: Accept-Encoding to every response it processes (when compression is enabled), including responses whose body it doesn't compress. A reader troubleshooting unexpected Vary headers on uncompressed responses — or reasoning about CDN cache variants — finds nothing here explaining it. The breaking-changes article has the full story, but nobody on the how-to page is sent there.

Feature announced in What's New:

The response compression middleware now appends Vary: Accept-Encoding to every response that passes through it, including responses for which it didn't compress the body. This gives downstream caches more correct cache keys.

Product source confirms the behavior: in ResponseCompressionBody.cs (ShouldCompressResponseCommon, L204–L227), the Vary: Accept-Encoding header is appended based on whether the response is a candidate for compression (a compressible content type), decoupled from whether a matching provider was actually selected for the request's Accept-Encoding. That's why the header now appears even when no encoding is applied.

Breaking change: Yes — documented at breaking-changes/11/response-compression-always-vary.md. The how-to note should cross-link it rather than restate the mitigation.


📁 Affected files

Item Path Lines Section
1. performance/response-compression.md 251–255 "Add the Vary header"

Target article uids: (none — performance/response-compression.md has no uid in front matter; link by relative path)


📝 Proposed changes

✏️ 1. Update — response-compression.md, add a >= aspnetcore-11.0 note in "Add the Vary header" (State D split)

Applies to: >= aspnetcore-11.0
Location: Lines 251–255 — after the existing Vary paragraph (L253) and before the [!INCLUDE...] (L255), inside the >= aspnetcore-6.0 zone (L227–L269).

Before (lines 251–255):

## Add the Vary header

When responses are compressed based on the [Accept-Encoding request header](https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/Accept-Encoding), there can be uncompressed and multiple compressed versions of the response. To instruct client and proxy caches that multiple versions exist and should be stored, the `Vary` header is added with an `Accept-Encoding` value. The response middleware [adds the 'Vary' header](https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/ResponseCompression/src/ResponseCompressionBody.cs#L198-L241) in the _ResponseCompressionBody.cs_ file automatically when the response is compressed.

[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)]

After:

## Add the Vary header

When responses are compressed based on the [Accept-Encoding request header](https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/Accept-Encoding), there can be uncompressed and multiple compressed versions of the response. To instruct client and proxy caches that multiple versions exist and should be stored, the `Vary` header is added with an `Accept-Encoding` value. The response middleware [adds the 'Vary' header](https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/ResponseCompression/src/ResponseCompressionBody.cs#L198-L241) in the _ResponseCompressionBody.cs_ file automatically when the response is compressed.

:::moniker-end

:::moniker range=">= aspnetcore-11.0"

> [!NOTE]
> In .NET 11 and later, the response compression middleware adds `Vary: Accept-Encoding` even to responses whose body it doesn't compress—for example, when the content type isn't configured for compression, or the client didn't request a supported encoding. In earlier versions the header was added only when the response was actually compressed. The broader behavior gives shared caches and CDNs a correct cache key so a stored compressed response isn't served to a client that didn't request that encoding. If the response already lists `Accept-Encoding` in its `Vary` header, the middleware doesn't add a duplicate. For details and mitigation, see [Response compression always emits Vary: Accept-Encoding](../breaking-changes/11/response-compression-always-vary.md).

:::moniker-end

:::moniker range=">= aspnetcore-6.0"

[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)]

Rationale: The existing paragraph is accurate for 6.0–10.0, so it stays under the >= aspnetcore-6.0 zone unchanged. The .NET 11 clarification is scoped to >= aspnetcore-11.0 via a four-directive split and cross-links the breaking-change article for mitigation guidance, avoiding duplication.


✅ 2. Update — TOC

No TOC change required. Both articles already exist in the TOC; the edit adds a note to one.


✅ Action plan

  1. Confirm element 1 (breaking-change article) — already published and accurate.
  2. Apply change 1 as a State D four-directive split; verify the >= aspnetcore-6.0 zone that opened at L227 still closes exactly once at L269 after the edit (net-zero balance).
  3. Confirm the relative link ../breaking-changes/11/response-compression-always-vary.md resolves (neither article has a uid, so xref isn't available).
  4. Build and confirm the moniker zones render and the note appears only under 11.0.

⚠️ Review considerations

  • Don't rewrite the 6.0 paragraph. It's tempting to "fix" the "when the response is compressed" wording in place, but that sentence is correct for 6.0–10.0. Correcting it globally would misstate older versions. The moniker-scoped note is the right tool.
  • State D balance. This is one clean split and no other issue in this .NET 11 coverage sweep edits this file, so the combined balance is simply the file's existing balance plus 0. (Contrast v11 update: OpenAPI 3.2.0 support (Microsoft.OpenApi 3.x breaking change) #37706, where three independent splits in customize-openapi.md had to compose and must be applied bottom-up.)
  • Product link uses the pinned SHA; the existing in-article link points at main#L198-L241 (unpinned) — left as-is since it's pre-existing content, but this issue's evidence link is pinned to 1fcd...#L204-L227.

🔗 References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions