Search code examples
vb.netdownloadstreamresponsezipoutputstream

VB.NEt cannot stream/download zip file to client


I have a PDF string list with strings in the form of "http://whatever.pdf" and need to create a zip file of them and stream it to client as a download.

The weird thing is if I create the zip (I'm using ZipOutputStream) and write the file to disk it works, I'm able to open the generated zip file and uncompress it with no problem, but if I stream it (what I need to do) I get a zip file of the same size but which does error when trying to open it.

CreateZip:

Private Function CreateZip(pdfPathList As List(Of String), fileRoot As String) As String

    Response.Clear()
    Response.BufferOutput = False
    Response.ContentType = "application/zip"
    Response.AddHeader("content-disposition", "attachment; filename=pdf.zip")

    Dim pdfPathListLocal As List(Of String) = Utility.DownloadFilesFromList(pdfPathList, fileRoot)
    Dim outputMemStream = Utility.GenerateZipOutpuStream(pdfPathListLocal)
    Dim zipName As String = Guid.NewGuid.ToString() & ".zip"
    outputMemStream.Position = 0
    'Utility.WriteMemoryStreamToDisk(outputMemStream, fileRoot & "\" & zipName) => This line creates a valid zip file on disk, but I need to avoid it.

    Response.AddHeader("Content-Length", outputMemStream.Length)
    Response.Write(outputMemStream) => Writes the file ok in downloads, but apparently corrupted.

    Return zipName

End Function

DownloadFilesFromList:

Public Shared Function DownloadFilesFromList(pdfPathList As List(Of String), fileRoot As String) As List(Of String)

    Dim pdfPathListLocal As New List(Of String)

    Dim Client As WebClient = New WebClient()
    For Each strFile In pdfPathList
        Dim sFile As String = Path.GetFileName(strFile)
        Dim localFile As String = fileRoot + "\" + sFile
        Client.DownloadFile(strFile, localFile)
        pdfPathListLocal.Add(localFile)
    Next

    Return pdfPathListLocal

End Function

GenerateZipOutpuStream:

Public Shared Function GenerateZipOutpuStream(pdfPathListLocal As List(Of String)) As MemoryStream

    Dim outputMemStream = New MemoryStream()
    Dim strmZipOutputStream = New ZipOutputStream(outputMemStream)
    strmZipOutputStream.SetLevel(9)

    Dim objCrc32 As New Crc32()

    For Each strFile In pdfPathListLocal
        Dim strmFile As FileStream = IO.File.OpenRead(strFile)
        Dim abyBuffer(Convert.ToInt32(strmFile.Length - 1)) As Byte
        strmFile.Read(abyBuffer, 0, abyBuffer.Length)

        Dim sFile As String = Path.GetFileName(strFile)
        Dim theEntry As ZipEntry = New ZipEntry(sFile)
        theEntry.DateTime = DateTime.Now
        theEntry.Size = strmFile.Length
        strmFile.Close()

        objCrc32.Reset()
        objCrc32.Update(abyBuffer)
        theEntry.Crc = objCrc32.Value

        strmZipOutputStream.PutNextEntry(theEntry)
        strmZipOutputStream.Write(abyBuffer, 0, abyBuffer.Length)

        'IO.File.Delete(strFile)
    Next

    strmZipOutputStream.Finish()

    Return outputMemStream

End Function

WriteMemoryStreamToDisk:

Public Shared Sub WriteMemoryStreamToDisk(outputMemStream As MemoryStream, file As String)

    Dim buffer As Byte() = outputMemStream.ToArray()
    Dim ms As New MemoryStream(buffer)
    Dim newFile As New FileStream(file, FileMode.Create, FileAccess.Write)
    ms.WriteTo(newFile)
    newFile.Close()
    ms.Close()

End Sub

What may be wrong? Any help?


Solution

  • OK, as I've finally found the root of my issue, I'll answer myself and I hope to be helpful to anyone else facing the same problem.

    The problem resides in the way aspx "UpdatePanel" works, it does a partial postback, that is not compatible con Response object and prevents it to work correctly.

    The solution is to disable partial postback for the specific element causing the issue, in my case:

    Adding this will cause aDownloadPdfs link to do a full postback and all Response object methods will work as expected.