I serve a bunch of static files in my app with app.UseStaticFiles()
. I'd like to inject some additional markup into the response for a particular HTML file before it's sent. My first attempt was to add middleware like this before the static files middleware:
app.Use(async (context, next) => {
await next();
// Modify the response here
});
However, this doesn't work as I can't actually read the read the response stream - it's using Kestrel's FrameResponseStream
under the hood, which is unreadable.
So, I figured I could replace the response body stream with a MemoryStream
that I could write to:
app.Use(async (context, next) => {
context.Response.Body = new MemoryStream();
await next();
// Modify the response here
});
But this just causes the request to never complete - it goes through all the pipeline stages, but it never even returns any headers to the browser.
So, is there any way I can modify the response that is produced by the StaticFileMiddleware
?
Update
As the HTML file in question is tiny (765 bytes), memory consumption isn't a concern. However, any attempts to read/modify the response still causes the same problem as before (nothing is returned). More explicitly, here's what's being done:
app.Use(async (context, next) => {
var originalStream = context.Response.Body;
var bufferStream = new MemoryStream();
context.Response.Body = bufferStream;
await next();
bufferStream.Seek(0, SeekOrigin.Begin);
if (/* some condition */)
{
var reader = new StreamReader(bufferStream);
var response = await reader.ReadToEndAsync();
// The response string is modified here
var writer = new StreamWriter(originalStream);
await writer.WriteAsync(response);
}
else
{
await bufferStream.CopyToAsync(originalStream);
}
});
The files hitting the else
condition are returned just fine, but the particular file in the if
condition causes trouble. Even if I don't modify the stream at all, it still hangs.
Yes, the default stream provided is read only because the data is only buffered for a short moment and flushed to the client. Hence you can't rewind or read it.
Your second attempt doesn't work because the original stream is never processed. You replaced the response body stream entirely with MemoryStream
and thrown away the original request, so there is never something written to it and the client waits forever.
You must not forget, that the stream to the client is within the original stream, you can't just replace it with something else.
After calling await next()
you must read the data from the MemoryStream
and then write it to the original stream.
app.Use(async (context, next) => {
var originalStream = context.Response.Body;
var memoryStream = new MemoryStream();
context.Response.Body = memoryStream;
await next();
// Here you must read the MemoryStream, modify it, then write the
// result into "originalStream"
});
But be aware, that this solution will buffer the whole response into the servers memory, so if you send large files this will significantly degenerate the performance of your ASP.NET Core application, especially if you serve files which are several megabytes in size and cause the garbage collection to be triggered more often.
And this wouldn't only affect your static files, but also all your regular requests, because the MVC Middleware is called after the static files middleware.
If you really want to modify a single (or a list of files) on each request, I'd rather suggest you doing this inside a controller and route certain files there. Remember, if the given file is not found by the static files middleware, it will call the next one in chain until it comes to the mvc middleware.
Just setup a route there that will match a specific file or folder and route it to a controller. Read the file in the controller and write it to the response stream or just return the new steam (using return File(stream, contentType);
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