Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to directly set response body to a file stream in ASP.NET Core middleware?

Sample code below to write a file stream to Response.Body in an ASP.NET Core middleware doesn't work (emits empty response):

public Task Invoke(HttpContext context)
{
    context.Response.ContentType = "text/plain";

    using (var fs = new FileStream("/valid-path-to-file-on-server.txt", FileMode.Open)
    using (var sr = new StreamReader(fs))
    {
        context.Response.Body = sr.BaseStream;
    }

    return Task.CompletedTask;
}

Any ideas what could be wrong with this approach of directly setting the context.Response.Body?

Note: any next middleware in the pipeline is skipped for no further processing.

Update (another example): a simple MemoryStream assignment doesn't work either (empty response):

context.Response.Body = new MemoryStream(Encoding.UTF8.GetBytes(DateTime.Now.ToString()));
like image 953
Nick Avatar asked Nov 01 '19 02:11

Nick


People also ask

What is HTTP request and Httpresponse in asp net?

When the server receives an HTTP request, it processes that request and responds to the client. The response tells the client if the request was successful or not. ASP.NET Core is a web application framework that runs on the server.

What is FromBody ASP.NET Core?

[FromBody] attributeThe ASP.NET Core runtime delegates the responsibility of reading the body to an input formatter. Input formatters are explained later in this article. When [FromBody] is applied to a complex type parameter, any binding source attributes applied to its properties are ignored.

What is HTTP context in ASP.NET Core?

The HttpContext encapsulates all the HTTP-specific information about a single HTTP request. When an HTTP request arrives at the server, the server processes the request and builds an HttpContext object. This object represents the request which your application code can use to create the response.

What is FileStreamResult C#?

FileContentResult. Represents an ActionResult that when executed will write a binary file to the response. FileStreamResult. Represents an ActionResult that when executed will write a file from a stream to the response.


1 Answers

  1. No. You can never do that directly.

    Note that context.Response.Body is a reference to an object (HttpResponseStream) that is initialized before it becomes available in HttpContext. It is assumed that all bytes are written into this original Stream. If you change the Body to reference (point to) a new stream object by context.Response.Body = a_new_Stream, the original Stream is not changed at all.

    Also, if you look into the source code of ASP.NET Core, you'll find the Team always copy the wrapper stream to the original body stream at the end rather than with a simple replacement(unless they're unit-testing with a mocked stream). For example, the SPA Prerendering middleware source code:

        finally
        {
            context.Response.Body = originalResponseStream;
            ...
    

    And the ResponseCachingMiddleware source code:

        public async Task Invoke(HttpContext httpContext)
        {
            ...
            finally
            {
                UnshimResponseStream(context);
            }
            ...
        }
    
        internal static void UnshimResponseStream(ResponseCachingContext context)
        {
            // Unshim response stream
            context.HttpContext.Response.Body = context.OriginalResponseStream;
    
            // Remove IResponseCachingFeature
            RemoveResponseCachingFeature(context.HttpContext);
        }
    
  2. As a walkaround, you can copy the bytes to the raw stream as below:

    public async Task Invoke(HttpContext context)
    {
        context.Response.ContentType = "text/plain";
        using (var fs = new FileStream("valid-path-to-file-on-server.txt", FileMode.Open))
        {
            await fs.CopyToAsync(context.Response.Body);
        }
    }
    

    Or if you like to hijack the raw HttpResponseStream with your own stream wrapper:

        var originalBody = HttpContext.Response.Body;
        var ms = new MemoryStream();
        HttpContext.Response.Body = ms;
        try
        {
            await next();
            HttpContext.Response.Body = originalBody;
            ms.Seek(0, SeekOrigin.Begin);
            await ms.CopyToAsync(HttpContext.Response.Body);
        }
        finally
        {
            response.Body = originalBody;
        }
    
like image 119
itminus Avatar answered Sep 18 '22 20:09

itminus