Search code examples
c#.netitext7

PdfDocument remains locked after closing


I have a windows service which merges PDFs together on the fly and then moves them to another location. I don't have control over what someone wants merged, for the most part. It has happened that every so often a corrupted PDF gets processed and therefore creating the new PdfDocument throws a PdfException "Trailer not found". I am catching the exception and closing the document but it appears after closing the PDF itself is still locked somehow. I need to delete the directory but in trying to do that it throws an IOException and crashes the service.

I have verified that calling the PdfDocument constructor is what locks the pdf and that immediately after closing the file remains locked.

Any ideas? Is there something iText can do to help with is or do I need to come up with some sort of work around where I check for corrupted PDFs up front?

ProcessDirectory


    private void ProcessDirectory(string directoryPath)
    {
        EventLogManager.WriteInformation("ProcessDirectory");
        // DON'T TOUCH THE BACKUPS, ERRORS AND WORK DIRECTORIES.  Just in case they were made or renamed after the fact for some reason
        if (directoryPath != this._errorsPath && directoryPath != this._backupsPath && directoryPath != this._workPath)
        {
            string pdfJsonPath = System.IO.Path.Combine(directoryPath, "pdf.json");

            if (File.Exists(pdfJsonPath))
            {
                string workPath = System.IO.Path.Combine(this._workPath, System.IO.Path.GetFileName(directoryPath));

                try
                {
                    CopyToDirectory(directoryPath, workPath);

                    PdfMerge pdfMerge = null;

                    string jsonPath = System.IO.Path.Combine(workPath, "pdf.json");
                    using (StreamReader r = Helpers.GetStreamReader(jsonPath))
                    {
                        string json = r.ReadToEnd();
                        pdfMerge = JsonConvert.DeserializeObject<PdfMerge>(json);
                    }

                    FillFormFields(workPath, pdfMerge);

                    if (pdfMerge.Pdfs.Any(p => !String.IsNullOrWhiteSpace(p.OverlayFilename)))
                    {
                        ApplyOverlays(workPath, pdfMerge);
                    }

                    MergePdfs(workPath, pdfMerge);
                    //NumberPages(workPath, pdfMerge);
                    FinishPdf(workPath, pdfMerge);

                    // Move original to backups directory
                    if (DoSaveBackups)
                    {
                        string backupsPath = System.IO.Path.Combine(this._backupsPath, String.Format("{0}_{1}", System.IO.Path.GetFileName(directoryPath), DateTime.Now.ToString("yyyyMMddHHmmss")));
                        Directory.Move(directoryPath, backupsPath);
                    }
                    else
                    {
                        Directory.Delete(directoryPath, true);
                    }
                }
                catch (Exception ex)
                {
                    EventLogManager.WriteError(ex);

                    if (DoSaveErrors)
                    {
                        // Move original to errors directory
                        string errorsPath = System.IO.Path.Combine(this._errorsPath, String.Format("{0}_{1}", System.IO.Path.GetFileName(directoryPath), DateTime.Now.ToString("yyyyMMddHHmmss")));
                        Directory.Move(directoryPath, errorsPath);
                    }
                    else
                    {
                        Directory.Delete(directoryPath, true);
                    }
                }

                // Delete work directory
                // THIS IS WHERE THE IOEXCEPTION OCCURS AND THE SERVICE CRASHES
                Directory.Delete(workPath, true);
            }
            else
            {
                EventLogManager.WriteInformation(String.Format("No pdf.json file.  {0} skipped.", directoryPath));
            }
        }
    }

FillFormFields


    private void FillFormFields(string directoryPath, PdfMerge pdfMerge)
    {
        if (pdfMerge != null && pdfMerge.Pdfs != null)
        {
            string formPath = String.Empty;
            string newFilePath;
            PdfDocument document = null;
            PdfAcroForm form;
            PdfFormField pdfFormField;

            foreach (var pdf in pdfMerge.Pdfs)
            {
                try
                {
                    formPath = System.IO.Path.Combine(directoryPath, pdf.Filename);
                    newFilePath = System.IO.Path.Combine(
                        directoryPath,
                        String.Format("{0}{1}", String.Format("{0}{1}", System.IO.Path.GetFileNameWithoutExtension(pdf.Filename), "_Revised"), System.IO.Path.GetExtension(pdf.Filename)));

                    // THIS IS WHERE THE PDFEXCEPTOIN OCCURS
                    document = new PdfDocument(Helpers.GetPdfReader(formPath), new PdfWriter(newFilePath));
                    form = PdfAcroForm.GetAcroForm(document, true);

                    if (pdf.Fields != null && pdf.Fields.Count > 0)
                    {
                        foreach (var field in pdf.Fields)
                        {
                            if (field.Value != null)
                            {
                                pdfFormField = form.GetField(field.Name);

                                if (pdfFormField != null)
                                {
                                    form.GetField(field.Name).SetValue(field.Value);
                                }
                                else
                                {
                                    EventLogManager.WriteWarning(String.Format("Field '{0}' does not exist in '{1}'", field.Name, pdf.Filename));
                                }
                            }
                        }
                    }

                    form.FlattenFields();
                }
                catch (Exception ex)
                {
                    throw new Exception(String.Format("An exception occurred filling form fields for {0}", pdf.Filename), ex);
                }
                finally
                {
                    if (document != null)
                    {
                        document.Close();
                    }
                }

                // Now rename the new one back to the old name
                File.Delete(formPath);
                File.Move(newFilePath, formPath);
            }
        }
    }

UPDATE

It seems in order to everything to dispose properly you have to declare separate PdfReader and PdfWriter objects into using statements and pass those into the PdfDocument. Like this:

    using (reader = Helpers.GetPdfReader(formPath))
    {
        using (writer = new PdfWriter(newFilePath))
        {
            using (document = new PdfDocument(reader, writer))
            {
                // The rest of the code here
            }
        }
    }

I'm not sure why this is other than that iText isn't disposing of the individual PdfReader and PdfWriter when disposing of the PdfDocument, which I assumed it would.


Solution

  • Find out which of the itext7 classes implement IDisposable (from the documentation, or the Visual Studio Object Browser etc), and make sure you use them within using blocks, the same way you already have using blocks for StreamReader.

    Edit: @sourkrause's solution can be shortened to:

    using (reader = Helpers.GetPdfReader(formPath))
    using (writer = new PdfWriter(newFilePath))
    using (document = new PdfDocument(reader, writer))
    {
        // The rest of the code here
    }