Skip to content
Merged
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions aspnetcore/fundamentals/middleware/request-response.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to read the request body and write the response body in A
monikerRange: '>= aspnetcore-3.0'
ms.author: tdykstra
ms.custom: mvc
ms.date: 5/29/2019
ms.date: 4/22/2025
uid: fundamentals/middleware/request-response
---
# Request and response operations in ASP.NET Core
Expand All @@ -25,7 +25,7 @@ There are two abstractions for the request and response bodies: <xref:System.IO.
* `TextWriter`
* `HttpResponse.WriteAsync`

Streams aren't being removed from the framework. Streams continue to be used throughout .NET, and many stream types don't have pipe equivalents, such as `FileStreams` and `ResponseCompression`.
Streams aren't being removed from the framework. Streams continue to be used throughout .NET, and many stream types don't have pipe equivalents, such as `FileStreams` and `ResponseCompression`, and straightforward adding compression to the stream.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that it is not possible to write a custom compression middleware with PipeWriter. Is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rick-Anderson could you also cover what I mentioned in #35288 (comment)

That's what I tried to do here (line 28 change).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that it is not possible to write a custom compression middleware with PipeWriter. Is that correct?

@gfoidl @martincostello ???

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not possible to write a custom compression middleware with PipeWriter

It's possible, but very awkward compared to Streams. W/ Streams it's quite easy, as the decorator pattern can be used to wrap a stream, which wraps a streams, etc.

W/ PipeWriter more custom code would be needed. E.g. an intermediate Pipeline that reads what the user's PipeWriter writes (and flushes), then perfom the compression and write (and flush) it the response body's PipeReader.
Also for compression .NET provides no easy to use infrastructure for this use case.

So as said it's possible, but w/ quite an effort. So I see no reason why one should use PipeWriter for this, when it's quite easy to accomplish with Streams.

Note: The Stream in the response is quite a simple adapter for the PipeWriter (at least in Kestrel), so there shouldn't be any perf-difference, especially when the data is sent over the network. So use either Body or BodyWriter depending on the use case and on how it can be done more easily.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Stream in the response is quite a simple adapter for the PipeWriter

I think this is the piece that I was missing. The pipe and stream are different APIs for the same channel of data? For example, if my endpoint middleware writes to pipe and my compression middleware wraps the stream, my data will be compressed?

Could we explicit this part in this document 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pipe and stream are different APIs for the same channel of data?

Yep.
Underlying is the network socket, from which data is written / read. In oder to make these operations easier to use and abstracted away (because instead of network socket it could also be named pipes, unix domain sockets, etc. -- effort was done in ASP.NET Core with so called "project bedrock") there's pipes and streams, which allow "unified" access higher up in the stack.

if my endpoint middleware writes to pipe and my compression middleware wraps the stream, my data will be compressed?

W/o knowing your code I'm not able to say yes or no.
Interleaving PipeWriter and BodyStream is dangerous, as none of these is thread-safe. They're not intended to be used concurrent or mixed in any way.
So it really depends when you write / flush the pipe and what you'll do with the stream.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's say my endpoint handler is just writing a protobuf object to the pipe

MyProtoObject.WriteTo(HttpContext.Response.BodyWriter)

and I use the standard response compression middleware

app.UseResponseCompression();

I'm expecting the compression middleware to decorate the stream and not touch the pipe. I'm wondering if my response will be compressed.


## Stream examples

Expand Down Expand Up @@ -70,7 +70,7 @@ These issues are fixable, but the code is becoming progressively more complicate

## Pipelines

The following example shows how the same scenario can be handled using a [PipeReader](/dotnet/standard/io/pipelines#pipe):
The following example shows how the preceding stream scenario can be handled using a [PipeReader](/dotnet/standard/io/pipelines#pipe):

[!code-csharp[](request-response/samples/3.x/RequestResponseSample/Startup.cs?name=GetListOfStringFromPipe)]

Expand All @@ -80,6 +80,13 @@ This example fixes many issues that the streams implementations had:
* Encoded strings are directly added to the list of returned strings.
* Other than the `ToArray` call, and the memory used by the string, string creation is allocation free.

When writing directly to `HttpResponse.BodyWriter`, call `PipeWriter.FlushAsync` manually to ensure the data is flushed to the underlying response response body. Here's why:

* `HttpResponse.BodyWriter` is a `PipeWriter` that buffers data until a flush operation is triggered.
* Calling `FlushAsync` writes the buffered data to the underlying response body

It's up to the developer to decide when to call `FlushAsync`, balancing factors such as buffer size, network write overhead, and whether the data should be sent in discrete chunks. For more information, see [System.IO.Pipelines in .NET](/dotnet/standard/io/pipelines).

## Adapters

The `Body`, `BodyReader`, and `BodyWriter` properties are available for `HttpRequest` and `HttpResponse`. When you set `Body` to a different stream, a new set of adapters automatically adapt each type to the other. If you set `HttpRequest.Body` to a new stream, `HttpRequest.BodyReader` is automatically set to a new `PipeReader` that wraps `HttpRequest.Body`.
Expand Down