C# DotNet Core Middleware Wrap Response

Review the comments to get an understanding of what you can do to wrap the response.

public async Task Invoke(HttpContext context) {
    //Hold on to original body for downstream calls
    Stream originalBody = context.Response.Body;
    try {
        string responseBody = null;
        using (var memStream = new MemoryStream()) {
            //Replace stream for upstream calls.
            context.Response.Body = memStream;
            //continue up the pipeline
            await next(context);
            //back from upstream call.
            //memory stream now hold the response data
            //reset position to read data stored in response stream
            memStream.Position = 0;
            responseBody = new StreamReader(memStream).ReadToEnd();
        }//dispose of previous memory stream.
        //lets convert responseBody to something we can use
        var data = JsonConvert.DeserializeObject(responseBody);
        //create your wrapper response and convert to JSON
        var json = new BaseResponse() { 
            data = data, 
            apiVersion = "1.2",
            otherInfoHere = "here"
        }.toJson();
        //convert json to a stream
        var buffer = Encoding.UTF8.GetBytes(json);
        using(var output = new MemoryStream(buffer)) {
            await output.CopyToAsync(originalBody);
        }//dispose of output stream
    } finally {
        //and finally, reset the stream for downstream calls
        context.Response.Body = originalBody;
    }
} 

In .NET Core 3.1 or .NET 5

  1. Create your response envelope object. Example:

    internal class ResponseEnvelope<T>
    {
      public T Data { set; get; }
      public string ApiVersion { set; get; }
      public string OtherInfoHere { set; get; }
    }
    
  2. Derive a class from ObjectResultExecutor

    internal class ResponseEnvelopeResultExecutor : ObjectResultExecutor
    {
     public ResponseEnvelopeResultExecutor(OutputFormatterSelector formatterSelector, IHttpResponseStreamWriterFactory writerFactory, ILoggerFactory loggerFactory, IOptions<MvcOptions> mvcOptions) : base(formatterSelector, writerFactory, loggerFactory, mvcOptions)
     {
     }
    
     public override Task ExecuteAsync(ActionContext context, ObjectResult result)
     {
         var response = new ResponseEnvelope<object>();
         response.Data = result.Value;
         response.ApiVersion = "v1";
         response.OtherInfoHere = "OtherInfo";
    
         TypeCode typeCode = Type.GetTypeCode(result.Value.GetType());
         if (typeCode == TypeCode.Object)
              result.Value = response;
    
         return base.ExecuteAsync(context, result);
      }
    }
    
  3. Inject into the DI like

    public void ConfigureServices(IServiceCollection services)
     {
         services.AddSingleton<IActionResultExecutor<ObjectResult>, ResponseEnvelopeResultExecutor>();
    

And the responses should have an envelope. This does not work with primitive types.