Search code examples
c#asp.net-web-apimemorystream

StreamContent not loaded to the end


I have (several) WebAPI action(s), which load QuickFix logs from database (via EF) and use this private method to return them as CSV:

private HttpResponseMessage BuildCsvResponse<T>(T[] entries, Func<T, string> row, string fileName)
{
    var response = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    var i = entries.Length;
    foreach (var entry in entries)
    {
        i--;
        writer.WriteLine(row(entry)); // simply call to overridden ToString() method
    }
    stream.Seek(0, SeekOrigin.Begin);
    stream.Flush();
    response.Content = new StreamContent(stream);
    response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
    {
        FileName = fileName,
    };
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
    return response;
}

The problem is that content is never loaded to the end and cut on random symbol not so far from end. Why could it happen?

May be it is important - all log strings contain delimiter 0x01


Solution

  • You need to Flush your streamwriter's internal buffers before you touch the underlying stream.

    Best is to tell your StreamWriter to keep the stream open by using another contructor. You can then safely dispose your streamwriter causing it to flush its buffer while your memorystream instance stays open and doesn't get disposed.

    Notice that you need to pick an encoding that matches your HTTP content response. I choose UTF8 here, adapt accordingly.

    var stream = new MemoryStream();
    // notice the true as last parameter, false is the default.
    using(var writer = new StreamWriter(stream, Encoding.UTF8, 8192, true))
    {
        var i = entries.Length;
        foreach (var entry in entries)
        {
            i--;
            writer.WriteLine(row(entry)); // simply call to overridden ToString() method
        }
    }
    // your streamwriter has now flushed its buffer and left the stream open
    stream.Seek(0, SeekOrigin.Begin);
    // calling Flush on the stream was never needed so I removed that.
    response.Content = new StreamContent(stream);