3

I want to write Custom Middleware in my ASP.NET Core 1.0 project which will replace original framework's Http Response Stream to my own, so I will be able to perform read / seek / write operations on it (first 2 are not possible on the original stream) in the further code i.e. in Actions or Filters.

I've started with the following code:

public class ReplaceStreamMiddleware
{
    protected RequestDelegate NextMiddleware;

    public ReplaceStreamMiddleware(RequestDelegate next)
    {
        NextMiddleware = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {       
        using (var responseStream = new MemoryStream())
        {
            var fullResponse = httpContext.Response.Body;
            httpContext.Response.Body = responseStream;
            await NextMiddleware.Invoke(httpContext);
            responseStream.Seek(0, SeekOrigin.Begin);
            await responseStream.CopyToAsync(fullResponse);
        }   
    }
}

The problem with the following code is that sometimes the fullResponse stream is already closed at the time of invoking await responseStream.CopyToAsync(fullResponse); so it throws an exception Cannot access a closed Stream.

This weird behaviour is easy to observe when I load the page in the browser and then refresh, before it loads completely.

I would like to know:

  1. why this happens?
  2. how to prevent it?
  3. is my solution a good idea or there is another way to replace response stream?

1 Answer 1

8

The exception doesn't come from your CopyToAsync. It's from one of your code's callers:

You're not restoring the original response stream in HttpContext. Therefore, whoever calls your middleware will get back a closed MemoryStream.

Here's some working code:

app.Use(async (httpContext, next) =>
{
    using (var memoryResponse = new MemoryStream())
    {
        var originalResponse = httpContext.Response.Body;
        try
        {
            httpContext.Response.Body = memoryResponse;

            await next.Invoke();

            memoryResponse.Seek(0, SeekOrigin.Begin);
            await memoryResponse.CopyToAsync(originalResponse);
        }
        finally
        {
            // This is what you're missing
            httpContext.Response.Body = originalResponse;
        }
    }
});

app.Run(async (context) =>
{
    context.Response.ContentType = "text/other";
    await context.Response.WriteAsync("Hello World!");
});
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.