Open
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
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:
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
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?)