Skip to content

Add API to help set and revert response bodies #31146

Open
@davidfowl

Description

@davidfowl

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

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractionsfeature-http-abstractions

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions