1

I'm creating PDF invoices in a C# MVC application that I'm attaching to an email and sending, ideally, once the email is sent I'd like to delete the invoice to free up server space and improve privacy/security. I've written code to do it but it fails 50% of the time because the file is locked by another process (I'm not sure if it's the create/write process that's locking it or the email send). I'm sending the email asynchronously (i.e. the code to delete doesn't execute until the email has sent).

I'd appreciate some tips on how to handle this problem. I could run a job to clean up old files but I'd prefer to clean them up as I go...

I forgot to mention I am using iTextSharp to generate the PDF - the crux of it is this code is used to generate the final invoice (I am able to delete the list of files passed in as a param without drama):

/// <summary>
        /// http://stackoverflow.com/questions/4276019/itextsharp-pdfcopy-use-examples
        /// </summary>
        /// <param name="fileNames"></param>
        /// <param name="outFile"></param>
        private void CombineMultiplePDFs(List<string> files, string outFile)
        {
            int pageOffset = 0;
            int f = 0;

            iTextSharp.text.Document document = null;
            PdfCopy writer = null;

            foreach (string file in files)
            {
                // we create a reader for a certain document
                PdfReader reader = new PdfReader(file);
                reader.ConsolidateNamedDestinations();
                // we retrieve the total number of pages
                int n = reader.NumberOfPages;

                pageOffset += n;

                if (f == 0)
                {
                    // step 1: creation of a document-object
                    document = new iTextSharp.text.Document(reader.GetPageSizeWithRotation(1));
                    // step 2: we create a writer that listens to the document
                    writer = new PdfCopy(document, new FileStream(outFile, FileMode.Create));
                    // step 3: we open the document
                    document.Open();
                }
                // step 4: we add content
                for (int i = 0; i < n; )
                {
                    ++i;
                    if (writer != null)
                    {
                        PdfImportedPage page = writer.GetImportedPage(reader, i);
                        writer.AddPage(page);
                    }
                }
                PRAcroForm form = reader.AcroForm;

                if (form != null && writer != null)
                {
                    writer.CopyAcroForm(reader);
                }

                f++;
            }

            // step 5: we close the document
            if (document != null)
            {
                document.Close();
            }
        }

The PDF File is then sitting on the server (e.g. "~/Invoices/0223.pdf") ready to attach to an email like so:

MailMessage mailMessage = new MailMessage();
        mailMessage.From = new MailAddress(WebConfig.GetWebConfigKey(AppSettingsKey.ReplyEmailAddress.ToString()));
        mailMessage.To.Add(new MailAddress(user.Email));
        mailMessage.Subject = emailTemplate.TemplateSubject;
        mailMessage.Body = emailTemplate.TemplateContent;
        mailMessage.IsBodyHtml = false;
        mailMessage.Attachments.Add(new Attachment(HttpContext.Current.Server.MapPath("/Invoices/" + invoiceId + ".pdf")));

        SmtpClient client = new SmtpClient();

        try
        {
            client.Send(mailMessage);
        }
        catch{...}{
            //Error handling
        }

        client.Dispose();

Then I attempt to delete it:

File.Delete(HttpContext.Current.Server.MapPath("/Invoices/" + invoiceId + ".pdf"));
Rob
  • 10,004
  • 5
  • 61
  • 91
  • I recommend using a JIT cleanup routine as a complement to, and not instead of, a scheduled cleanup job. Things will go unexpected in the long run, and you don't want garbage that noone will ever bother to look at again left behind. – Jon May 17 '12 at 11:52
  • Post some code, so we can see what is going on. – Oded May 17 '12 at 11:53
  • Can you create and attach the PDF as a stream? That way you don't have to delete anything. I've done that before with DynamicPDF and Aspose. – Thinking Sites May 17 '12 at 12:06
  • 1
    @ThinkingSites probably means the `MemoryStream` class - using it avoids the PDF data from touching the filesystem at all. – Oded May 17 '12 at 12:11
  • 1
    @ThinkingSites - actually this is promising approach - I can change the follow line writer = new PdfCopy(document, new FileStream(outFile, FileMode.Create)); to use a memorystream instead. – Rob May 17 '12 at 12:21
  • 1
    Oded, that's correct. A memory stream is the best way to do this IMHO. I'm posting it as an answer. – Thinking Sites May 17 '12 at 12:26

4 Answers4

6

Instead of saving the file to disk, and incurring performance, IO, and deletion issues, see if your PDF and mail libraries support writing the PDFs to a MemoryStream, and attaching that stream to the email.

Thinking Sites
  • 3,494
  • 17
  • 30
3

Are you using a FileStream to open/read the file ?

You could try to use a Stream that inherit from FileStream, and delete the file when the stream close:

/// <summary>
/// FileStream that automatically delete the file when closing
/// </summary>
public class AutoDeleteFileStream : FileStream
{
    private string _fileName;

    public AutoDeleteFileStream(string fileName, FileMode fileMode, FileAccess fileAccess)
        : base(fileName, fileMode, fileAccess)
    {
        this._fileName = fileName;
    }

    public AutoDeleteFileStream(string fileName, FileMode fileMode)
        : base(fileName, fileMode)
    {
        this._fileName = fileName;
    }

    public override void Close()
    {
        base.Close();
        if (!string.IsNullOrEmpty(_fileName))
            File.Delete(_fileName);
    }
}
Fabske
  • 2,106
  • 18
  • 33
0

I have the same issue of you and what i did was move the PDF file to another directory like /trash and then delete the file of this directory. This solve the problem for me.

mart
  • 224
  • 2
  • 13
-1

You could save the file in your app DB. This way, you wouldn't get lock by another process problem. You could then delete it. Also, you could put a timestamp on the row, this way, if your app breaks, it will be easier to remove old files in the future.

Alex Rouillard
  • 777
  • 8
  • 9