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
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; } }
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); } }
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.