dotnet / dotnet/aspnetcore

Add API to help set and revert response bodies

Open
#31,146 12 comments 5 reactions 1 assignee Claimed by @davidfowl View on GitHub
api-approved area-networking feature-http-abstractions
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

## Background and Motivation

A very common pattern in ASP.NET Core middleware (and other middleware like components) is to set a temporary response body, execute some user code, then revert to the previous response body when unwinding. The steps go like this:

- Store a reference to the existing body
- Replace the body with the new stream
- Execute user code
- Grab the contents of the body stream and copy it to the previous stream
- Restore the previous body in a finally so exceptional cases are handled
- Don't forget to dispose any streams created

``` C#
app.Use(async (context, next) =>
{
var previous = context.Response.Body;
try
{
using var stream = new FileBufferingWriteStream();
context.Response.Body = stream;
await next();
await stream.CopyToAsync(previous);
}
finally
{
context.Response.Body = previous;
}
});
```

This code today is very manual and we could instead make it a bit more pleasant with some new APIs.

## Proposed API

Please provide the specific public API signature diff that you are proposing. For example:

```diff
namespace Microsoft.AspNetCore.Http
{
public class HttpResponse
{
+ public ResponseBodyScope UseBody(Stream stream);
}

+ public struct ResponseBodyScope : IDisposable, IAsyncDisposable
+ {
+ public Stream PreviousBody { get; }
+ public Stream CurrentBody { get; }
+ public void Dispose();
+ public ValueTask DisposeAsync();
+ }
}
```

## Usage Examples

``` C#
app.Use(async (context, next) =>
{
using var scope = context.Response.UseBody(new FileBufferingWriteStream());
await next();
await scope.CurrentBody.CopyToAsync(scope.PreviousBody);
});
```

## Alternative Designs

An alternative might be an explicit push/pop style API but that wouldn't automagically handle the pop in the failure case.

We may also want to consider a mode that auto-flushes to the previous response body but then failure cases also get weird (did you want to flush the response on failure?)

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.