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;
}
context.Response.OnStarting()
works as well, but not when modifying responses. Also I don't like usingOnStarting()
, because it breaks the iterative middleware workflow. – Unbrace