How to stream with ASP.NET Core

70,707

Solution 1

To stream a response that should appear to the browser like a downloaded file, you should use FileStreamResult:

[HttpGet]
public FileStreamResult GetTest()
{
  var stream = new MemoryStream(Encoding.ASCII.GetBytes("Hello World"));
  return new FileStreamResult(stream, new MediaTypeHeaderValue("text/plain"))
  {
    FileDownloadName = "test.txt"
  };
}

Solution 2

@Developer4993 was correct that to have data sent to the client before the entire response has been parsed, it is necessary to Flush to the response stream. However, their answer is a bit unconventional with both the DELETE and the Synchronized.StreamWriter. Additionally, Asp.Net Core 3.x will throw an exception if the I/O is synchronous. This is tested in Asp.Net Core 3.1:

[HttpGet]
public async Task Get()
{
    Response.ContentType = "text/plain";
    StreamWriter sw;
    await using ((sw = new StreamWriter(Response.Body)).ConfigureAwait(false))
    {
        foreach (var item in someReader.Read())
        {
            await sw.WriteLineAsync(item.ToString()).ConfigureAwait(false);
            await sw.FlushAsync().ConfigureAwait(false);
        }
    }
}

Assuming someReader is iterating either database results or some I/O stream with a large amount of content that you do not want to buffer before sending, this will write a chunk of text to the response stream with each FlushAsync(). For my purposes, consuming the results with an HttpClient was more important than browser compatibility, but if you send enough text, you will see a chromium browser consume the results in a streaming fashion. The browser seems to buffer a certain quantity at first.

Where this becomes more useful is with the latest IAsyncEnumerable streams, where your source is either time or disk intensive, but can be yielded a bit at at time:

[HttpGet]
public async Task<EmptyResult> Get()
{
    Response.ContentType = "text/plain";
    StreamWriter sw;
    await using ((sw = new StreamWriter(Response.Body)).ConfigureAwait(false))
    {
        await foreach (var item in GetAsyncEnumerable())
        {
            await sw.WriteLineAsync(item.ToString()).ConfigureAwait(false);
            await sw.FlushAsync().ConfigureAwait(false);
        }
    }
    return new EmptyResult();
}

You can throw an await Task.Delay(1000) into either foreach to demonstrate the continuous streaming.

Finally, @StephenCleary 's FileCallbackResult works the same as these two examples as well. It's just a bit scarier with the FileResultExecutorBase from deep in the bowels of the Infrastructure namespace.

[HttpGet]
public IActionResult Get()
{
    return new FileCallbackResult(new MediaTypeHeaderValue("text/plain"), async (outputStream, _) =>
    {
        StreamWriter sw;
        await using ((sw = new StreamWriter(outputStream)).ConfigureAwait(false))
        {
            foreach (var item in someReader.Read())
            {
                await sw.WriteLineAsync(item.ToString()).ConfigureAwait(false);
                await sw.FlushAsync().ConfigureAwait(false);
            }
        }
        outputStream.Close();
    });
}

Solution 3

It is possible to return null or EmptyResult() (which are equivalent), even when previously writing to Response.Body. It may be useful if the method returns ActionResult to be able to use all the other results aswell (e.g. BadQuery()) easily.

[HttpGet("test")]
public ActionResult Test()
{
    Response.StatusCode = 200;
    Response.ContentType = "text/plain";
    using (var sw = new StreamWriter(Response.Body))
    {
        sw.Write("something");
    }
    return null;
}

Solution 4

I was wondering as well how to do this, and have found out that the original question's code actually works OK on ASP.NET Core 2.1.0-rc1-final, neither Chrome (and few other browsers) nor JavaScript application do not fail with such endpoint.

Minor things I would like to add are just set StatusCode and close the response Stream to make the response fulfilled:

[HttpGet("test")]
public void Test()
{
    Response.StatusCode = 200;
    Response.ContentType = "text/plain";
    using (Response.Body)
    {
        using (var sw = new StreamWriter(Response.Body))
        {
            sw.Write("Hi there!");
        }
    }
}

Solution 5

This question is a bit older, but I couldn't find a better answer anywhere for what I was trying to do. To send the currently buffered output to the client, you must call Flush() for each chunk of content you would like to write. Simply do the following:

[HttpDelete]
public void Content()
{
    Response.StatusCode = 200;
    Response.ContentType = "text/html";

    // the easiest way to implement a streaming response, is to simply flush the stream after every write.
    // If you are writing to the stream asynchronously, you will want to use a Synchronized StreamWriter.
    using (var sw = StreamWriter.Synchronized(new StreamWriter(Response.Body)))
    {
        foreach (var item in new int[] { 1, 2, 3, 4, })
        {
            Thread.Sleep(1000);
            sw.Write($"<p>Hi there {item}!</p>");
            sw.Flush();
        }
    };
}

you can test with curl using the following command: curl -NX DELETE <CONTROLLER_ROUTE>/content

Share:
70,707
Dmitry Nogin
Author by

Dmitry Nogin

Updated on July 16, 2020

Comments

  • Dmitry Nogin
    Dmitry Nogin almost 4 years

    How to properly stream response in ASP.NET Core? There is a controller like this (UPDATED CODE):

    [HttpGet("test")]
    public async Task GetTest()
    {
        HttpContext.Response.ContentType = "text/plain";
        using (var writer = new StreamWriter(HttpContext.Response.Body))
            await writer.WriteLineAsync("Hello World");            
    }
    

    Firefox/Edge browsers show

    Hello World

    , while Chrome/Postman report an error:

    The localhost page isn’t working

    localhost unexpectedly closed the connection.

    ERR_INCOMPLETE_CHUNKED_ENCODING

    P.S. I am about to stream a lot of content, so I cannot specify Content-Length header in advance.

  • Dmitry Nogin
    Dmitry Nogin about 7 years
    Actually my data source is not a Stream - it is SomeReader or IEnumerable. I will need a Stream adaptor then, which is pretty cumbersome. Is there an easy way to just write to Response.Body? P.S. It does work in Chrome for some guys :)
  • Dmitry Nogin
    Dmitry Nogin about 7 years
    Your code will not work because you dispose source stream before it gets used.
  • Stephen Cleary
    Stephen Cleary about 7 years
  • spender
    spender over 6 years
    Still not good because, technically, the stream won't get read until after this method has returned. However, given that a MemoryStream's Dispose doesn't dispose of the contents of the stream, you'll get away with it here. With real IO, you probably won't be so lucky.
  • spender
    spender over 6 years
    ...actually, FileStreamResult disposes the stream it was passed when it has finished. The using statement here is completely superfluous.
  • Edward Brey
    Edward Brey almost 6 years
    Are you sure the using statements are required? Doesn't ASP.NET Core dispose of them for you. Instead, I would expect you need to call StreamWriter.Close so that the final output gets flushed. (Dispose doesn't flush.)
  • Poul Bak
    Poul Bak over 5 years
    Why is this an answer to the question?
  • Heemanshu Bhalla
    Heemanshu Bhalla over 5 years
    Please try adding some explanation to answer that may help
  • Martin Staufcik
    Martin Staufcik over 5 years
    I added explanation into the answer.
  • Dustin Malone
    Dustin Malone over 5 years
    Downvoting. For starters, the code example will not produce streaming results - as was the question. Instead it only streams the data into a buffered response... Not really helpful for streaming a response. Second, the explanation that is there is not really relevant.
  • emirhosseini
    emirhosseini about 5 years
    Outdated with latest version of .net core.
  • Stephen Cleary
    Stephen Cleary about 5 years
    @emirhosseini: FileStreamResult doesn't exist in the latest version?
  • marco6
    marco6 over 4 years
    @EdwardBrey while I agree that the "using" on Response.Body is not needed, can you back-up your statement on StreamWriter? The code seems to just call Dispose(true) on Close() (.Net: github.com/microsoft/referencesource/blob/master/mscorlib/… .NetCore: github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/‌​… ). It would be very unreasonable otherwise in my opinion.
  • AlreadyLost
    AlreadyLost over 4 years
    .Net Core doesn't have Flush capabilities
  • Suren
    Suren over 4 years
    @AlreadyLost it doesn't work without Flush(), gives it a shot. [asp.net core 2.2]
  • mdisibio
    mdisibio almost 4 years
    Tested @StephenCleary 's FileCallbackResult with Asp.Net Core 3.1 and it streams continuous results to the consumer as the other StreamWriter answers here do. It's basically a replacement for the old PushStreamContent. Does not necessarily appear to consumer as a file..but as a stream of text (or whatever).
  • Thanasis Ioannidis
    Thanasis Ioannidis over 3 years
    But this copies an existing read-stream to the body stream. How to write to the body stream directly?
  • Stephen Cleary
    Stephen Cleary over 3 years
    @ThanasisIoannidis: I wrote a FileCallbackResult that may do what you need.
  • m5c
    m5c about 3 years
    @emirhosseini You may just use return new FileStreamResult(stream, "text/plain")
  • Bigyan Devkota
    Bigyan Devkota almost 3 years
    what do you mean by someReader.Read()? can you please show the implementation?
  • mdisibio
    mdisibio almost 3 years
    @BigyanDevkota someReader.Read() is simply a placeholder for anything that knows how to iterate over a large source data and return small chunks of data without loading the entire source into memory first. Could be a StreamReader or DbReader or GetAsyncEnumerable() or your own implementation. The question is about streaming the data to a browser, not how to obtain the data.
  • Bigyan Devkota
    Bigyan Devkota almost 3 years
    i didnt have to implement that, this 3 lines worked for me using (var sw = new StreamWriter(Response.Body)) { sw.Write("something"); sw.Flush(); }, thanks though
  • Mohan Raj Raja
    Mohan Raj Raja about 2 years
    How to stream the part of byte to the front end for video player
  • Stephen Cleary
    Stephen Cleary about 2 years
    @MohanRajRaja: Designing a video streaming solution is really a different kind of question.