Memory stream to file error

david chase 41 Reputation points
2021-08-20T14:21:05.41+00:00

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
ASP.NET
ASP.NET
A set of technologies in the .NET Framework for building web applications and XML web services.
3,288 questions
0 comments No comments
{count} votes

Accepted answer
  1. Michael Taylor 48,821 Reputation points
    2021-08-20T15:36:51.157+00:00

    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.

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. david chase 41 Reputation points
    2021-08-21T17:41:27.833+00:00

    @Michael Taylor
    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?