My requirement: write a middleware that filters all "bad words" out of a response that comes from another subsequent middleware (e.g. Mvc).
The problem: streaming of the response. So when we come back to our FilterBadWordsMiddleware
from a subsequent middleware, which already wrote to the response, we are too late to the party... because response started already sending, which yields to the wellknown error response has already started
...
So since this is a requirement in many various situations -- how to deal with it?
.NET Core 3+ solution with proper resource handling:
Replace response stream by MemoryStream
to prevent its sending. Return the original stream after the response is modified:
public async Task Invoke(HttpContext context)
{
var response = context.Response;
//uncomment this line to re-read context.Request.Body stream
//context.Request.EnableBuffering();
var originBody = response.Body;
using var newBody = new MemoryStream();
response.Body = newBody;
await _next(context);
await ModifyResponseAsync(response);
newBody.Seek(0, SeekOrigin.Begin);
await newBody.CopyToAsync(originBody);
response.Body = originBody;
}
private async Task ModifyResponseAsync(HttpResponse response)
{
var stream = response.Body;
//uncomment to re-read the response stream
//stream.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(stream, leaveOpen: true);
string originalResponse = await reader.ReadToEndAsync();
//add modification logic
string modifiedResponse = "Hello from Stackoverflow";
stream.SetLength(0);
using var writer = new StreamWriter(stream, leaveOpen: true);
await writer.WriteAsync(modifiedResponse);
await writer.FlushAsync();
response.ContentLength = stream.Length;
}
Original .NET Core 1 answer
Replace response stream by MemoryStream
to prevent its sending. Return the original stream after the response is modified:
public async Task Invoke(HttpContext context)
{
bool modifyResponse = true;
Stream originBody = null;
if (modifyResponse)
{
//uncomment this line only if you need to read context.Request.Body stream
//context.Request.EnableRewind();
originBody = ReplaceBody(context.Response);
}
await _next(context);
if (modifyResponse)
{
//as we replaced the Response.Body with a MemoryStream instance before,
//here we can read/write Response.Body
//containing the data written by middlewares down the pipeline
//finally, write modified data to originBody and set it back as Response.Body value
ReturnBody(context.Response, originBody);
}
}
private Stream ReplaceBody(HttpResponse response)
{
var originBody = response.Body;
response.Body = new MemoryStream();
return originBody;
}
private void ReturnBody(HttpResponse response, Stream originBody)
{
response.Body.Seek(0, SeekOrigin.Begin);
response.Body.CopyTo(originBody);
response.Body = originBody;
}
It's a workaround and it can cause performance problems. I hope to see a better solution here.