question

davidchase-4554 avatar image
0 Votes"
davidchase-4554 asked cooldadtx commented

Memory stream to file error

We are using the code below to merge multiple PDF files (using iTextSharp) into one stream and then display that stream in a web page. It has been working great but now we want to output that memory stream to a single pdf file like it displays and we now get the error "Cannot access a closed Stream." on the line below

mem.WriteTo(file)

     Public Shared Sub MergePDFs(ByVal files As List(Of String), ByVal filename As String)
         Using mem As New MemoryStream()
             Dim readers As New List(Of PdfReader)
             Using doc As New Document
                 Dim copy As New PdfCopy(doc, mem)
                 copy.SetMergeFields()
                 doc.Open()
                 For Each strfile As String In files
                     Dim reader As New PdfReader(strfile)
                     copy.AddDocument(reader)
                     readers.Add(reader)
                 Next
             End Using
    
             For Each reader As PdfReader In readers
                 reader.Close()
             Next
    
             If filename = "finalbillfiles.pdf" Then
                 filename = "E:/" & filename
    
                 Dim file As New FileStream(filename, FileMode.Create, FileAccess.Write)
                 mem.WriteTo(file)
                 file.Close()
             End If
    
             HttpContext.Current.Response.Clear()
             HttpContext.Current.Response.ContentType = "application/pdf"
             HttpContext.Current.Response.AppendHeader("Content-Disposition", "inline; filename=" + filename)
             HttpContext.Current.Response.BinaryWrite(mem.ToArray)
             HttpContext.Current.Response.OutputStream.Flush()
    
         End Using
     End Sub
dotnet-aspnet-webforms
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

cooldadtx avatar image
0 Votes"
cooldadtx answered

It's confusing but read the docs for MemoryStream.ToArray. This method can be called after the stream is closed because, while MemoryStream does implement IDisposable since it is a Stream, the method doesn't actually do anything other than set a flag.

The issue is that when Document is disposed it automatically disposes the stream you passed to it. That is the RAII pattern that you might have heard of. Since you gave a lifetime-managed object to Document it is responsible for cleaning it up. You actually don't need your Using on the stream at all. But that also means you need to ensure the Document stays around until you are done with the stream.

Here's the updated version of your code.

Public Shared Sub MergePDFs(ByVal files As List(Of String), ByVal filename As String)
         Using mem As New MemoryStream()
             Dim readers As New List(Of PdfReader)
             Using doc As New Document
                 Dim copy As New PdfCopy(doc, mem)
                 copy.SetMergeFields()
                 doc.Open()
                 For Each strfile As String In files
                     Dim reader As New PdfReader(strfile)
                     copy.AddDocument(reader)
                     readers.Add(reader)
                 Next
                 
                 If filename = "finalbillfiles.pdf" Then
                     filename = "E:/" & filename
    
                     Using file As New FileStream(filename, FileMode.Create, FileAccess.Write)
                         mem.WriteTo(file)
                     End Using
                 End If
             End Using

             For Each reader As PdfReader In readers
                 reader.Close()
             Next    
    
             HttpContext.Current.Response.Clear()
             HttpContext.Current.Response.ContentType = "application/pdf"
             HttpContext.Current.Response.AppendHeader("Content-Disposition", "inline; filename=" + filename)
             HttpContext.Current.Response.BinaryWrite(mem.ToArray)
             HttpContext.Current.Response.OutputStream.Flush()
    
         End Using
     End Sub


Basically ensure Document persists until you're done with the stream (except ToArray) and because iTextSharp doesn't really manage resources well you also need to keep the PdfReader instances open until after the document is closed.

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

davidchase-4554 avatar image
0 Votes"
davidchase-4554 answered cooldadtx commented

@cooldadtx
I tried your suggestion from your code sample and it no longer threw an error. However, the pdf file that is created in the mem.WriteTo(file) is not a valid PDF file. What am I missing as the files merged are all PDF files?

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

In general that means one or more streams haven't flushed all their data yet. Without looking into the details of how iTextSharp implemented their logic I think the easiest thing to do is adjust your code to capture the data after the stream is closed which means you cannot use WriteTo anymore.

' Remove the Using file as New FileStream code
...
For Each reader As PdfReader In readers
      reader.Close()
 Next

' Stream is closed but we can still call ToArray
 Dim data As Byte() = mem.ToArray

' Replace WriteTo call with this 
File.WriteAllBytes(filename, data)

` In Http response use `data` with BinaryWrite
0 Votes 0 ·