9

I get the following error message when I try to build a PDF file using iTextSharp with multiple tables:

Cannot access a closed Stream.

Here is my code:

//Create a byte array that will eventually hold our final PDF
Byte[] bytes;

List<TableObject> myTables = getTables();
TableObject currentTable = new TableObject();

//Boilerplate iTextSharp setup here
//Create a stream that we can write to, in this case a MemoryStream
using (MemoryStream ms = new MemoryStream())
{
    //Create an iTextSharp Document which is an abstraction of a PDF but **NOT** a PDF
    using (Document doc = new Document(PageSize.A4, 10f, 10f, 10f, 0f))
    {
        foreach (TableObject to in myTables)
        {
            //Create a writer that's bound to our PDF abstraction and our stream
            using (PdfWriter writer = PdfWriter.GetInstance(doc, ms))
            {
                if (!doc.IsOpen())
                {
                    //Open the document for writing
                    doc.Open();
                }
                //Get the data from database corresponding to the current tableobject and fill all the stuff we need!
                DataTable dt = getDTFromID(to._tableID);
                Object[] genObjects = new Object[5];
                genObjects = gen.generateTable(dt, currentTable._tableName, currentTable._tableID.ToString(), currentTable, true);

                StringBuilder sb = (StringBuilder)genObjects[1];
                String tableName = sb.ToString();
                Table myGenTable = (Table)genObjects[0];
                String table = genObjects[2].ToString();

                using (StringReader srHtml = new StringReader(table))
                {
                    //Parse the HTMLiTextSharp.tool.xml.XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, srHtml);
                }

                //should give empty page at the end, need to fix it later
                doc.NewPage();
            }

        }
        doc.Close();
    }

    //After all of the PDF "stuff" above is done and closed but **before** we
    //close the MemoryStream, grab all of the active bytes from the stream
    bytes = ms.ToArray();
}

//Now we just need to do something with those bytes.
Response.ContentType = "application/pdf";
Response.AppendHeader("Content-Disposition", "attachment; filename=Report_complete.pdf");
Response.BinaryWrite(bytes);

Here is the complete stacktrace from my asp.net application:

[ObjectDisposedException: Cannot access a closed Stream.]
System.IO.__Error.StreamIsClosed() +57
System.IO.MemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count) +11011171 iTextSharp.text.pdf.OutputStreamCounter.Write(Byte[] buffer, Int32 offset, Int32 count) +52
iTextSharp.text.pdf.PdfIndirectObject.WriteTo(Stream os) +53
iTextSharp.text.pdf.PdfBody.Write(PdfIndirectObject indirect, Int32 refNumber, Int32 generation) +100
iTextSharp.text.pdf.PdfBody.Add(PdfObject objecta, Int32 refNumber, Int32 generation, Boolean inObjStm) +385
iTextSharp.text.pdf.PdfWriter.AddToBody(PdfObject objecta, PdfIndirectReference refa) +51
iTextSharp.text.pdf.Type1Font.WriteFont(PdfWriter writer, PdfIndirectReference piref, Object[] parms) +317
iTextSharp.text.pdf.FontDetails.WriteFont(PdfWriter writer) +296
iTextSharp.text.pdf.PdfWriter.AddSharedObjectsToBody() +180
iTextSharp.text.pdf.PdfWriter.Close() +86
iTextSharp.text.DocWriter.Dispose() +10
System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +51 System.Web.UI.Control.OnLoad(EventArgs e) +92
System.Web.UI.Control.LoadRecursive() +54
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +772

The bytes-Array should be accessable inside the using statements, but it looks like that there is the error.

I've tried moving the foreach loop inside the using(writer ...) block:

//Create a byte array that will eventually hold our final PDF
//must be outside of the foreach loop (and everything else), because we store every single generated table in here for the final pdf!!
Byte[] bytes;

List<TableObject> myTables = getTables();
TableObject currentTable = new TableObject();

//Boilerplate iTextSharp setup here
//Create a stream that we can write to, in this case a MemoryStream
using (MemoryStream ms = new MemoryStream())
{
    //Create an iTextSharp Document which is an abstraction of a PDF but **NOT** a PDF
    using (Document doc = new Document(PageSize.A4, 10f, 10f, 10f, 0f))
    {
            //Create a writer that's bound to our PDF abstraction and our stream
            using (PdfWriter writer = PdfWriter.GetInstance(doc, ms))
            {
                //loop all tableobjects inside the document & the instance of PDFWriter itself! 
                foreach (TableObject to in myTables)
                {
                    //only happens on the first run!
                    if (!doc.IsOpen())
                    {
                        //Open the document for writing
                        doc.Open();
                    }
                    //Get the data from database corresponding to the current tableobject and fill all the stuff we need!
                    DataTable dt = getDTFromID(to._tableID);
                    Object[] genObjects = new Object[5];
                    genObjects = gen.generateTable(dt, currentTable._tableName, currentTable._tableID.ToString(), currentTable, true);

                    StringBuilder sb = (StringBuilder)genObjects[1];
                    String tableName = sb.ToString();
                    Table myGenTable = (Table)genObjects[0];
                    String table = genObjects[2].ToString();

                    using (StringReader srHtml = new StringReader(table))
                    {
                        //Parse the HTML
                        iTextSharp.tool.xml.XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, srHtml);
                    }

                    //this will probably render a whole new page at the end of the file!! need to be fixed later!!!
                    doc.NewPage();
                }
                //After all of the PDF "stuff" above is done and closed but **before** we
                //close the MemoryStream, grab all of the active bytes from the stream
                bytes = ms.ToArray();
            }
        doc.Close();
    }


}

//Now we just need to do something with those bytes.
Response.ContentType = "application/pdf";
Response.AppendHeader("Content-Disposition", "attachment; filename=ShiftReport_complete.pdf");
Response.BinaryWrite(bytes);

But I still get the same error.

Hack4Life
  • 563
  • 1
  • 11
  • 34
  • 1
    Try moving your `foreach` inside of `using (PdfWriter writer...` – Chris Haas Dec 01 '15 at 16:34
  • @Hack4Life have you tried Chris' proposal? That will definitively fix a number of issues in your code. – mkl Dec 01 '15 at 17:33
  • *I've tried moving the foreach loop inside the using(writer ...) block:* - and? Did something change? I assume some error still occurs, try replacing the lines `bytes = ms.ToArray();` and `doc.Close();` with each other. – mkl Dec 02 '15 at 14:08
  • @mkl sorry for that, I have forgotten to tell you that it still gives the same error. question updated. Switching those two statements still does not change anything. `Cannot access a closed Stream.` – Hack4Life Dec 04 '15 at 12:13
  • I just tried to reproduce your issue. Obviously I had to remove all the code working with your custom datastructures, instead of your `foreach` loop I used a simple `for (int dummy = 1; dummy < 5; dummy++)`, and instead of your retrieving and adding some data table structure, I simply added `new Paragraph(dummy.ToString())`. I did get an exception, too, which could be eradicated by not putting the `PdfWriter` into a `using` statement. – mkl Dec 04 '15 at 13:37
  • **BUT** I did get *a different exception* than the one you posted. Thus, I assume that in your case there actually is some exception thrown in your code retrieving and adding some data table structure which (when descending through the `using` blocks) triggers some follow-up problem there. Thus, I'd advise you too first get everything working with some dummy code as mine, and only when that works, I'd introduce your data retrieving and table building code. – mkl Dec 04 '15 at 13:41
  • I removed the `using` statement from the `PDFWriter` and this is the point where I get the following exception: `Collection was modified; enumeration operation may not execute.`. Which exception do you get with your dummy data? I use the same code that is inside my `foreach` loop for only writing one `TableObject` into a PDF file (in another method) and it works fine. – Hack4Life Dec 04 '15 at 13:55

6 Answers6

19

PdfWriter closes the stream by default. Just add the following line after PdfWriter.GetInstance

writer.CloseStream = false;
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • I tried it and throws the following exception `Collection was modified; enumeration operation may not execute.` I've added `writer.CloseStream = false;` after the opening bracket – Hack4Life Dec 02 '15 at 13:02
  • i did this and it worked. i also added a writer.close() at the bottom of my code block to manually close it, even though this may be cleaned up at the end of the sub automatically, just want to be safe. – Taylor Brown Aug 08 '17 at 15:51
  • after adding this line I got actual error thrown, thanks – Harish Nayak Oct 07 '19 at 04:29
1

The solution was simple, just putting a .ToList() to my collection in the foreach loop:

foreach (TableObject to in myTables.ToList())
{
     //some code stuff

This answer under this question helped me to solve this problem.

Community
  • 1
  • 1
Hack4Life
  • 563
  • 1
  • 11
  • 34
0

There is a possibility that using block on PdfWriter writer.. closes the underlying stream ms when it tries to dispose writer.

Try removing using PdfWriter writer without using block and see. It should solve the issue.

Nikhil Vartak
  • 5,002
  • 3
  • 26
  • 32
0

The using command causes your MemoryStream to be disposed, so by the time you access it, the managed and unmanaged resources have already been released. Put this code just after the closing bracket for your foreach loop:

bytes = ms.ToArray();
user8128167
  • 6,929
  • 6
  • 66
  • 79
0

I was getting "Cannot acces a closed stream" with xmlworker (iTextSharp html to pdf) when I had an empty table without rows:

 <table>
    @if (false) 
    {
       <tr>
           <td>Foo</td>
       </tr>
    }
 </table>
sports
  • 7,851
  • 14
  • 72
  • 129
-1

The issue appears to be due to the PdfWriter being disposed before the Document is closed. Call doc.Close() inside the PdfWriter's using statement should resolve the issue.

kevinpo
  • 1,853
  • 19
  • 20
  • No and yes. The main issue was the one solved in the accepted answer because this threw an exception which then was hidden by a follow-up exception. Other than that, though, the code contains a number of errors in using the iText API, one of them the one you point out. – mkl Oct 31 '18 at 19:13