ASP.NET MVC: is it a good idea to return a File result from a byte[]?
I have an action in one of my controllers which creates a downloadable zip file which should be served to the user. Currently my code looks something like this:
using(var memoryStream = new MemoryStream()) {
// ... use SharpZipLib to write zip file content to the above MemoryStream ...
return File(memoryStream.ToArray(), "application/zip", "file.zip");
}
I am wondering if it is a good idea go convert memoryStream to a byte[]
, I guess this takes up more memory then using the stream? There is an overload to File()
which takes a Stream
object, and I passed in my memoryStream variable, but then jus开发者_JS百科t a blank page showed up.
Ideally I dont have to use a FileStream
and write a file to disk.
If you really care about memory then here's a solution that will directly write to the response stream. First define your custom ActionResult:
public class SharpZipLibResult : FileResult
{
private readonly string _fileDownloadName;
private readonly string[] _filesToZip;
private const int ChunkSize = 1024;
public SharpZipLibResult(string fileDownloadName, params string[] filesToZip)
: base("application/octet-stream")
{
_fileDownloadName = fileDownloadName;
_filesToZip = filesToZip;
}
protected override void WriteFile(HttpResponseBase response)
{
var cd = new ContentDisposition();
cd.FileName = _fileDownloadName;
response.AddHeader("Content-Disposition", cd.ToString());
response.BufferOutput = false;
using (var zipStream = new ZipOutputStream(response.OutputStream))
{
foreach (var file in _filesToZip)
{
var entry = new ZipEntry(Path.GetFileName(file));
zipStream.PutNextEntry(entry);
using (var reader = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
byte[] buffer = new byte[ChunkSize];
int bytesRead;
while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0)
{
byte[] actual = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, actual, 0, bytesRead);
zipStream.Write(actual, 0, actual.Length);
}
}
}
}
}
}
With this technique you could serve some really huge zip files without caring about memory or having to clean some temporary zip files on your server hard drives.
And finally your controller action could look like this:
public ActionResult Index()
{
return new SharpZipLibResult(
"result.zip",
@"c:\work\report1.pdf",
@"c:\work\report2.pdf",
@"c:\work\report3.pdf"
);
}
Using this method memory footprint is minimized because the zip is written directly to the response stream which in terms will be represented by an underlying network socket.
Of course depending on where your files are stored the SharpZipLibResult
could be tweaked. Here I assume the files are stored on the file system.
Using the MemoryStream as a stream instead of getting the array elliminates copying all of the content at once. Reading the stream also involves copying, but that is in smaller chunks.
Set the position of the memory stream to the beginning before reading from it:
memoryStream.Position = 0;
精彩评论