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?
You can also create a custom middleware to handle functionality on your response object, such as designing a new header. Here, the . setHeader() method will apply the new header, Success , on each function call.
Middleware vs FiltersFilters are a part of MVC, so they are scoped entirely to the MVC middleware. Middleware only has access to the HttpContext and anything added by preceding middleware. In contrast, filters have access to the wider MVC context, so can access routing data and model binding information for example.
Middleware is software that's assembled into an app pipeline to handle requests and responses. Each component: Chooses whether to pass the request to the next component in the pipeline. Can perform work before and after the next component in the pipeline.
Replace a response stream to 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.
A simpler version based on the code I used:
/// <summary>
/// The middleware Invoke method.
/// </summary>
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
/// <returns>A Task to support async calls.</returns>
public async Task Invoke(HttpContext httpContext)
{
var originBody = httpContext.Response.Body;
try
{
var memStream = new MemoryStream();
httpContext.Response.Body = memStream;
await _next(httpContext).ConfigureAwait(false);
memStream.Position = 0;
var responseBody = new StreamReader(memStream).ReadToEnd();
//Custom logic to modify response
responseBody = responseBody.Replace("hello", "hi", StringComparison.InvariantCultureIgnoreCase);
var memoryStreamModified = new MemoryStream();
var sw = new StreamWriter(memoryStreamModified);
sw.Write(responseBody);
sw.Flush();
memoryStreamModified.Position = 0;
await memoryStreamModified.CopyToAsync(originBody).ConfigureAwait(false);
}
finally
{
httpContext.Response.Body = originBody;
}
}
Unfortunately I'm not allowed to comment since my score is too low. So just wanted to post my extension of the excellent top solution, and a modification for .NET Core 3.0+
First of all
context.Request.EnableRewind();
has been changed to
context.Request.EnableBuffering();
in .NET Core 3.0+
And here's how I read/write the body content:
First a filter, so we just modify the content types we're interested in
private static readonly IEnumerable<string> validContentTypes = new HashSet<string>() { "text/html", "application/json", "application/javascript" };
It's a solution for transforming nuggeted texts like [[[Translate me]]] into its translation. This way I can just mark up everything that needs to be translated, read the po-file we've gotten from the translator, and then do the translation replacement in the output stream - regardless if the nuggeted texts is in a razor view, javascript or ... whatever. Kind of like the TurquoiseOwl i18n package does, but in .NET Core, which that excellent package unfortunately doesn't support.
...
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
var contentType = context.Response.ContentType?.ToLower();
contentType = contentType?.Split(';', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); // Filter out text/html from "text/html; charset=utf-8"
if (validContentTypes.Contains(contentType))
{
using (var streamReader = new StreamReader(context.Response.Body))
{
// Read the body
context.Response.Body.Seek(0, SeekOrigin.Begin);
var responseBody = await streamReader.ReadToEndAsync();
// Replace [[[Bananas]]] with translated texts - or Bananas if a translation is missing
responseBody = NuggetReplacer.ReplaceNuggets(poCatalog, responseBody);
// Create a new stream with the modified body, and reset the content length to match the new stream
var requestContent = new StringContent(responseBody, Encoding.UTF8, contentType);
context.Response.Body = await requestContent.ReadAsStreamAsync();//modified stream
context.Response.ContentLength = context.Response.Body.Length;
}
}
//finally, write modified data to originBody and set it back as Response.Body value
await ReturnBody(context.Response, originBody);
}
...
private Task ReturnBody(HttpResponse response, Stream originBody)
{
response.Body.Seek(0, SeekOrigin.Begin);
await response.Body.CopyToAsync(originBody);
response.Body = originBody;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With