ASP.NET Core in .NET 11 adds Zstandard to its response compression and request decompression middleware.
A client can ask for a compressed CSV with Accept-Encoding: zstd and upload the same bytes with Content-Encoding: zstd.
In both cases the endpoint handles ordinary text.
Which Direction Is Compressed?
On the download, the client advertises which encodings it can read.
On the upload, it declares how it encoded the file it sent.
Sending Accept-Encoding alone on the upload leaves the server with no indication that the request body needs decoding.
| Operation | Request header | What crosses the wire |
|---|---|---|
| GET the export uncompressed | No Accept-Encoding header | Plain CSV in this example |
| GET the export using Zstandard | Accept-Encoding: zstd | A CSV response with Content-Encoding: zstd |
| POST an already compressed CSV | Content-Encoding: zstd | Compressed request bytes. The handler reads CSV. |
Zstandard is a built-in provider in ASP.NET Core 11.
Register the services and add the middleware in Program.cs.
Wire Up a Download and an Import
Create the project with dotnet new web -n HttpOrders -f net11.0 --no-https and replace Program.cs with this code.
The download serves CSV as text/plain, a MIME type the middleware compresses by default.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResponseCompression();
builder.Services.AddRequestDecompression();
var app = builder.Build();
app.UseRequestDecompression();
app.UseResponseCompression();
app.MapGet("/orders.csv", () =>
Results.Text("id,total\n1,42\n2,18\n", "text/plain"));
app.MapPost("/imports", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var count = 0;
while (await reader.ReadLineAsync() is not null)
{
count++;
}
return Results.Text($"{count} lines");
});
app.Run();
From the project directory, run dotnet run --no-launch-profile --urls http://127.0.0.1:48719.
The demo runs over HTTP on localhost.
The middleware skips HTTPS responses by default.
Check the Wire Bytes With curl
In a second terminal, download the CSV twice and compare the files after decoding the second one.
You'll need curl, cmp, and the zstd CLI.
curl -fsS http://127.0.0.1:48719/orders.csv -o plain.csv
curl -fsS -D response.headers -H 'Accept-Encoding: zstd' \
http://127.0.0.1:48719/orders.csv -o response.zst
zstd -dc response.zst | cmp - plain.csv
Kestrel sent Content-Encoding: zstd and Vary: Accept-Encoding on the second response.
The first had no Content-Encoding header.
After decoding, cmp found the files identical.
The original CSV was 19 bytes. The compressed version was 31 bytes, so this export got larger. Compare both sizes on a full order export before you enable compression for it.
Leave curl --compressed off here.
It tells curl to decode the response, which would leave plain CSV in the file named response.zst.
Send Those Bytes Back to the API
Send response.zst back unchanged, with Content-Type set to the type of the original file.
curl -fsS -H 'Content-Encoding: zstd' -H 'Content-Type: text/csv' \
--data-binary @response.zst http://127.0.0.1:48719/imports
The endpoint returns 3 lines for the header and two orders.
As the handler reads HttpRequest.Body, the request decompression middleware decodes the stream.
Set a request body limit that accounts for how large an upload can get after decompression.
Why Might Production Still Return Plain Text?
Start at the reverse proxy.
It may handle compression itself or change the headers it forwards to Kestrel.
If Kestrel handles compression, check the Accept-Encoding header it receives, the response MIME type, and whether the app has replaced the default providers.
A second difference from localhost is HTTPS. The response compression middleware skips HTTPS by default because compressing a response that contains both a secret and user-controlled content can leak information about the secret.
Summary
Keep the download and upload as a pair of checks when a proxy or partner client changes.
The download should have a Content-Encoding: zstd header and decode to the original CSV.
Sending those same compressed bytes back should give you the original line count.
If the partner wants to keep a .zst archive after the HTTP transfer, create the file with ZstandardStream.
Frequently Asked Questions
Does installing ASP.NET Core 11 automatically compress HTTP traffic?
No. Zstandard is a built-in provider in ASP.NET Core 11. Register the compression services and add the middleware. For a response, the client must advertise a supported encoding and the response must meet the middleware rules.
Which header should I send for a Zstandard upload?
Send Content-Encoding: zstd for a body you have already compressed. Keep the Content-Type of the underlying data, such as text/csv. Accept-Encoding describes which encodings you can receive in the response.
What does a Minimal API receive when request decompression is enabled?
For a supported single encoding, the middleware wraps HttpRequest.Body with a decoder and removes the consumed Content-Encoding header before the endpoint runs. Reading the stream yields the original content.

