Question

Back in the WebForms days, I could use Response.OutputStream.Write() and Response.Flush() to chunk file data to the client - because the files we are streaming are huge and will consume too much web server memory. How can I do that now with the new MVC classes like FileStreamResult?

My exact situation is: The DB contains the file data (CSV or XLS) in a VarBinary column. In the WebForms implementation, I send down a System.Func to the DataAccess layer, which would iterate through the IDataReader and use the System.Func to stream the content to the client. The point is that I don't want the webapp to have to have any specific DB knowledge, including IDataReader.

How can I achieve the same result using MVC?

The Func is (that I define in the web layer and send down to the DB layer):

Func<byte[], long, bool> partialUpdateFunc = (data, length) =>
    {
        if (Response.IsClientConnected)
        {
            // Write the data to the current output stream.
            Response.OutputStream.Write(data, 0, (int) length);

            // Flush the data to the HTML output.
            Response.Flush();

            return true;
        }
        else
        {
            return false;
        }
    };

and in the DB Layer, we get the IDataReader from the DB SP (using statement with ExecuteReader):

using (var reader = conn.ExecuteReader())
{
    if (reader.Read())
    {
        byte[] outByte = new byte[BufferSize];
        long startIndex = 0;
        // Read bytes into outByte[] and retain the number of bytes returned.
        long retval = reader.GetBytes(0, startIndex, outByte, 0, BufferSize);

        // Continue while there are bytes beyond the size of the buffer.
        bool stillConnected = true;
        while (retval == BufferSize)
        {
            stillConnected = partialUpdateFunc(outByte, retval);
            if (!stillConnected)
            {
                break;
            }

            // Reposition start index to end of last buffer and fill buffer.
            startIndex += BufferSize;
            retval = reader.GetBytes(0, startIndex, outByte, 0, BufferSize);
        }

        // Write the remaining buffer.
        if (stillConnected)
        {
            partialUpdateFunc(outByte, retval);
        }
    }

    // Close the reader and the connection.
    reader.Close();
}
Was it helpful?

Solution

If you want to reuse FileStreamResult you need to create Stream-derived class that reads data from DB and pass that stream to the FileStreamResult.

Couple issues with that approach

  • action results are executed synchronously, so your download will not release thread while data is read from DB/send - may be ok for small number of parallel downloads. To get around you may need to use handler or download from async action directly (feels wrong for MVC approach)
  • at least old versions of FileStreamResult did not have "streaming" support (discussed here), make sure you are fine with that.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top