Skip to content

Latest commit

 

History

History
94 lines (60 loc) · 5.36 KB

request-response.md

File metadata and controls

94 lines (60 loc) · 5.36 KB
title author description monikerRange ms.author ms.custom ms.date uid
Request and Response operations in ASP.NET Core
rick-anderson
Learn how to read the request body and write the response body in ASP.NET Core.
>= aspnetcore-3.0
riande
mvc
5/29/2019
fundamentals/middleware/request-response

Request and response operations in ASP.NET Core

By Justin Kotalik

This article explains how to read from the request body and write to the response body. Code for these operations might be required when writing middleware. Outside of writing middleware, custom code isn't generally required because the operations are handled by MVC and Razor Pages.

There are two abstractions for the request and response bodies: xref:System.IO.Stream and xref:System.IO.Pipelines.Pipe. For request reading, xref:Microsoft.AspNetCore.Http.HttpRequest.Body?displayProperty=nameWithType is a xref:System.IO.Stream, and HttpRequest.BodyReader is a xref:System.IO.Pipelines.PipeReader. For response writing, xref:Microsoft.AspNetCore.Http.HttpResponse.Body?displayProperty=nameWithType is a xref:System.IO.Stream, and HttpResponse.BodyWriter is a xref:System.IO.Pipelines.PipeWriter.

Pipelines are recommended over streams. Streams can be easier to use for some simple operations, but pipelines have a performance advantage and are easier to use in most scenarios. ASP.NET Core is starting to use pipelines instead of streams internally. Examples include:

  • FormReader
  • TextReader
  • 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.

Stream examples

Suppose the goal is to create a middleware that reads the entire request body as a list of strings, splitting on new lines. A simple stream implementation might look like the following example:

Warning

The following code:

  • Is used to demonstrate the problems with not using a pipe to read the request body.
  • Is not intended to be used in production apps.

[!code-csharp]

[!INCLUDEabout the series]

This code works, but there are some issues:

  • Before appending to the StringBuilder, the example creates another string (encodedString) that is thrown away immediately. This process occurs for all bytes in the stream, so the result is extra memory allocation the size of the entire request body.
  • The example reads the entire string before splitting on new lines. It's more efficient to check for new lines in the byte array.

Here's an example that fixes some of the preceding issues:

Warning

The following code:

  • Is used to demonstrate the solutions to some problems in the preceding code while not solving all the problems.
  • Is not intended to be used in production apps.

[!code-csharp]

This preceding example:

  • Doesn't buffer the entire request body in a StringBuilder unless there aren't any newline characters.
  • Doesn't call Split on the string.

However, there are still a few issues:

  • If newline characters are sparse, much of the request body is buffered in the string.
  • The code continues to create strings (remainingString) and adds them to the string buffer, which results in an extra allocation.

These issues are fixable, but the code is becoming progressively more complicated with little improvement. Pipelines provide a way to solve these problems with minimal code complexity.

Pipelines

The following example shows how the same scenario can be handled using a PipeReader:

[!code-csharp]

This example fixes many issues that the streams implementations had:

  • There's no need for a string buffer because the PipeReader handles bytes that haven't been used.
  • 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.

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.

StartAsync

HttpResponse.StartAsync is used to indicate that headers are unmodifiable and to run OnStarting callbacks. When using Kestrel as a server, calling StartAsync before using the PipeReader guarantees that memory returned by GetMemory belongs to Kestrel's internal xref:System.IO.Pipelines.Pipe rather than an external buffer.

Additional resources