Asp.net Exception of type 'System.OutOfMemoryException
Hi Guys,
I am using asp.net mvc application with VB.net and I am trying to zip files and stream them back to the user to download.
Here is what I am doing
Public Function DownloadFiles(attachmentsSelected As String) As ActionResult Dim obj As FileDownloads = New FileDownloads() ''get file list of files to download Dim filesCol = obj.GetFile(attachmentsSelected).ToList() Using memoryStream = New MemoryStream() Using ziparchive = New ZipArchive(memoryStream, ZipArchiveMode.Create, True) For i As Integer = 0 To filesCol.Count - 1 //get the error here ziparchive.CreateEntryFromFile(filesCol(i).FilePath + "\" + filesCol(i).FileName, filesCol(i).FileName, CompressionLevel.Optimal) Next End Using Return File(memoryStream.ToArray(), "application/zip", "Attachments.zip") End Using End Function
The issue with this code is when I am trying to download large file ...let's say 500mb I get an exception.
"Exception of type 'System.OutOfMemoryException' was thrown."
Any idea how can I fix this issue.
Thank you.
ASP.NETVisual Basic.NET.NET Programming
Last Comment
Moti Mashiah
8/22/2022 - Mon
Snarf0001
You're basically just eating up too much memory as the entire file contents of 500 are stored in your memory stream.
You can get around it with a bit of a rework. Solution is to use the Response.OutputStream directly as the target of the ZipArchive. The hiccup, is that the .net implementation "thinks" that it needs to be a seekable stream, and returns an error if you try to do that.
But it doesn't actually have to be seekable. If you make a stream wrapper class that overrides the "CanSeek" property, and use that to pass the outputstream directly to the ziparchive you don't consume any memory, and you don't have the lag of creating the zip in the first place.
Apologies, I have the class in c# not vb, but I'm sure you can pick up the critical parts.
public class ZipStreamWrapper : Stream { public ZipStreamWrapper(Stream stream) { baseStream = stream; length = 0; } private int length; private Stream baseStream; public override bool CanRead { get { return true; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return true; } } public override long Length { get { return length; } } public override long Position { get { return length; } set { throw new NotImplementedException(); } } public override void Flush() { baseStream.Flush(); } public override int Read(byte[] buffer, int offset, int count) { return baseStream.Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } public override void Write(byte[] buffer, int offset, int count) { baseStream.Write(buffer, offset, count); length += count; } }
You're using a contiguous region of memory to load the stream, so on large files you're pretty much always going to get that exception. You'll want to use some sort of FileStream.
Writing directly to the output stream will be far better on resources, speed and latency than using an intermediate filestream.
Try the stream wrapper as suggested.
You can get around it with a bit of a rework. Solution is to use the Response.OutputStream directly as the target of the ZipArchive. The hiccup, is that the .net implementation "thinks" that it needs to be a seekable stream, and returns an error if you try to do that.
But it doesn't actually have to be seekable. If you make a stream wrapper class that overrides the "CanSeek" property, and use that to pass the outputstream directly to the ziparchive you don't consume any memory, and you don't have the lag of creating the zip in the first place.
Apologies, I have the class in c# not vb, but I'm sure you can pick up the critical parts.
Open in new window
And then just create the archive with this instead:
Open in new window