Link to home
Start Free TrialLog in
Avatar of Moti Mashiah
Moti MashiahFlag for Canada

asked on

asp.net download files.

Hi Guys,
I am using asp.net mvc.
I built a method in  the backend that let me stream file and download to my dom:
Here is my backend method.

Public Function DownloadFiles(attachmentsSelected As String, zipFileName As String) As FilePathResult
        Dim obj As FileDownloads = New FileDownloads()
        ''get file list user choose to download
        Dim filesCol = obj.GetFile(attachmentsSelected).ToList()

        Dim fileSavePath As String = IO.Path.Combine(System.Web.Hosting.HostingEnvironment.MapPath("~/DownLoadAttachments"), zipFileName)
        Using stream = New FileStream(fileSavePath, FileMode.Create)
            Using ziparchive = New ZipArchive(stream, ZipArchiveMode.Create, True)

                For i As Integer = 0 To filesCol.Count - 1
                    'ziparchive.CreateEntryFromFile(filesCol(i).FilePath + "\" + filesCol(i).FileName, filesCol(i).FileName, CompressionLevel.Optimal)
                    ziparchive.CreateEntryFromFile(filesCol(i).FilePath, filesCol(i).FileName, CompressionLevel.Optimal)
                Next
            End Using
        End Using

        Return File(fileSavePath, "application/zip", "Attachments.zip")
    End Function

Open in new window


Now from the front end I do something like that.

 window.location = "/DocumentViewer/DownloadFiles?attachmentsSelected=" + func.downloadFilesList + "&zipFileName=" + uuid;

Open in new window


The issue I have with this approach is - I want to delete the file from my server after the client download it so I need another call to the server ..let's say with ajax and delete, but the problem is I can't predict the time when this URL finish the process of downloading in the backend

Does anyone have an idea how to do it? I was trying Promise but it doesn't really work in this scenario.

Thank you.,
Avatar of Snarf0001
Snarf0001
Flag of Canada image

That's again why I suggested the wrapping the output stream in your previous question.
Then it writes directly to the users' browser.
No file is created on the server, no resources are eaten up, there's nothing to clean up, and there's no latency for the user while you're creating the zip.

If you go with that approach, you don't have to do anything else.
Avatar of Moti Mashiah

ASKER

Please, can you give me an example I don't know what you mean when you say wrapping the output?
Please, see my code in the backend and I will be happy if you can give me an example depending on this code.
 
Btw, Now I am using the FileStream which solved my memory leak as you know before I used MemoryStream.


Thank you so much for help.
Sure, but same thing as in the last question.  You should be writing directly to the output stream.  As it is, you're creating a file on disk, writing everything the file, then transferring the file to the download stream.  And the user ends up waiting while that entire process is happening, and also has a risk of a timeout.

The "target" result would be doing "new ZipArchive(Response.OutputStream)", but that throws an exception, because the ZipArchive is demanding a readable stream, which of course the output stream isn't.
But - as long as you're just creating and downloading, it you never actually NEED it to be read or the position to be set.  So the solution is to create a stream wrapper object that passes all calls directly to the output stream, while "pretending" to be readable..

    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;
        }
    }

Open in new window


Then you just call
new ZipArchive(new ZipStreamWrapper(Response.OutputStream), ZipArchiveMode.Create))

Open in new window


Passing in "ZipStreamWrapper(ResponseOutputStream)" instead of the filestream.
When you start adding your files to the ziparchive, it's writing them directly to the outputstream and downloading continuously as you're adding.
And then there's nothing to clean afterwards.
Thank you soooooo much for your answer.
I created this class you provided and I did something like:

    Public Function DownloadFiles(attachmentsSelected As String, zipFileName As String) As FileStreamResult
        Dim obj As FileDownloads = New FileDownloads()
        ''get file list user choose to download
        Dim filesCol = obj.GetFile(attachmentsSelected).ToList()
 Using fileStrimCreator = New ZipArchive(New ZipStreamWrapper(Response.OutputStream), ZipArchiveMode.Create)
            For i As Integer = 0 To filesCol.Count - 1
                fileStrimCreator.CreateEntry(filesCol(i).FilePath, CompressionLevel.Optimal)
            Next
 End Using
        Return New FileStreamResult(Response.OutputStream, "application/zip")
    End Function

Open in new window


I am getting some error in the return. I am not sure if I do the return right.
Please, can you help me with it.
Thank you.
exception: Specified method is not supported
ASKER CERTIFIED SOLUTION
Avatar of Snarf0001
Snarf0001
Flag of Canada image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi Snar,
Thank you again.
Now I see the all full scenario and in my understanding I still need to save the file on the disk right? then I copy the file to stream.
k, I got it you were thinking that i save the file in this download folder but I already do it in GetFile() function.
so I will try it now and let you know.

Thank you.
It's working like a charm.
You are a life savior :).

Thank you sooooooooooooo much
No problem, glad to help ;)
Was helpful and solved my solution perfectly.

Thank you again.