Link to home
Start Free TrialLog in
Avatar of bogorman
bogorman

asked on

Script can generate corrupted pdf file

The attached coding generates a pdf file. Unfortunately sometimes the file is corrupted - it appears to have a size of zero bytes and I cannot delete it using FileZilla or Internet Explorer 7 (via FTP).
Have been in touch with the web hosters and they say that:

"It appears it may have to be the way the file is betting created. For some reason it corrupts the permissions on the file, and they look like they're right, but they really are not. You may want to look over the way the file is being created."

Can anyone suggest a solution?

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using iTextSharp.text;
using iTextSharp.text.pdf;
using MySql.Data.MySqlClient;
 
 
public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        
        Document doc = new Document(PageSize.A4, 12, 15, 30, 0);
        string pdfFileLocation = @"D:\inetpub\DomainID54847\ConvertToPdf\Test.pdf";
        
        PdfWriter.GetInstance(doc, new System.IO.FileStream(pdfFileLocation, System.IO.FileMode.Create));
        //PdfWriter.GetInstance(doc, new System.IO.FileStream("Test.pdf", System.IO.FileMode.Create));
        doc.Open();
        
        //PdfPTable table = new PdfPTable(2);
        PdfPTable table = new PdfPTable(3);
 
        //actual width of table in points
        table.TotalWidth = 580f;
        
        //fix the absolute width of the table
        table.LockedWidth = true;
 
        
        //relative col widths in proportions - 1/3 and 2/3
        //float[] widths = new float[] { 1f, 2f };
 
        //relative widths 1/3 1/3 1/3
        float[] widths = new float[] { 1f, 1f, 1f };
 
 
        table.SetWidths(widths);
        table.HorizontalAlignment = 0;
        //leave a gap before and after the table
        //table.SpacingBefore = 42f;
        //table.SpacingAfter = 30f;
 
        table.DefaultCell.Border = 0;
        table.DefaultCell.PaddingLeft = 5;
        
        
        float TopPad = 12.5F;
        table.DefaultCell.PaddingTop = TopPad;
        
        table.DefaultCell.PaddingBottom = 30;
       
        
        PdfPCell cell = new PdfPCell();
 
       
        cell.FixedHeight = 100;
 
        //cell.Colspan = 2;
        cell.Colspan = 3;
        cell.Border = 0;
        cell.HorizontalAlignment = 1;
       
 
 
        //table.AddCell(cell);   NB MAY HAVE TO UNQUOTE THIS!!!!!
 
        BaseFont bfTimes = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, false);
        Font times = new Font(bfTimes, 10);
 
        int UserID;
        if (!int.TryParse(Request.QueryString["UserID"], out UserID))
        {
            Console.WriteLine("User ID not passed to page");     // Handle error
 
        }
 
        
        
        // Create the select command string
        // The school name is reduced in length to 38 characters to avoid it spilling over to two lines on the label
 
        string cmdstring = "select fldContact, Name, If(trim(addr1)='','.  ', addr1) as ad1, If(trim(addr2)='','.  ',addr2) as ad2," +
            "If(trim(addr3)='','.  ',addr3) as ad3, If(trim(addr4)='','.  ',addr4) as ad4, If(trim(addr5)='','.  ',addr5) as ad5 " +
            "FROM (" +
            "select fldContact, LEFT(trim(fldName),38) AS Name, fldADDR1, fldADDR2, fldTOWN, fldCOUNTY, fldPOSTCODE, ad," +
               "REPLACE(SUBSTRING(SUBSTRING_INDEX(ad, char(26), 1)," +
                                 "LENGTH(SUBSTRING_INDEX(ad, char(26), 1 -1)) + 1)," +
                                 "char(26), '') as addr1," +
               "REPLACE(SUBSTRING(SUBSTRING_INDEX(ad, char(26), 2)," +
                                 "LENGTH(SUBSTRING_INDEX(ad, char(26), 2 -1)) + 1)," +
                                 "char(26), '') as addr2," +
               "REPLACE(SUBSTRING(SUBSTRING_INDEX(ad, char(26), 3)," +
                                 "LENGTH(SUBSTRING_INDEX(ad, char(26), 3 -1)) + 1)," +
                                 "char(26), '') as addr3," +
               "REPLACE(SUBSTRING(SUBSTRING_INDEX(ad, char(26), 4), " +
                                 "LENGTH(SUBSTRING_INDEX(ad, char(26), 4 -1)) + 1)," +
                                 "char(26), '') as addr4," +
               "REPLACE(SUBSTRING(SUBSTRING_INDEX(ad, char(26), 5)," +
                                 "LENGTH(SUBSTRING_INDEX(ad, char(26), 5 -1)) + 1)," +
                                 "char(26), '') as addr5 " +
          "from (" +
        "SELECT fldContact, fldName, fldADDR1, fldADDR2, fldTOWN, fldCOUNTY, fldPOSTCODE," +
               "concat_ws(char(26),nullif(trim(fldADDR1),'')," +
                                  "nullif(trim(fldADDR2),'')," +
                                  "nullif(trim(fldTOWN),'')," +
                                  "nullif(trim(fldCOUNTY),'')," +
                                  "nullif(trim(fldPOSTCODE),'')) as ad " +
          "FROM tblschools RIGHT JOIN tblselectedschools ON tblschools.fldSCHOOL_ID = tblselectedschools.fldSCHOOL_ID " +
        "WHERE tblselectedschools.fldUserID = " + UserID.ToString() + " AND tblselectedschools.fldSelected = 1 ORDER BY fldName, fldPOSTCODE) as v" +
        ") as v1;";    
 
 
 
         
        
        
        // Create the Connectionstring
     // PersistSecurityInfo = false means that the password is not displayed in the
     // connectionstring of the MySqlCommand.
     //
 
    
       
 
    MySqlConnectionStringBuilder connBuilder = new MySqlConnectionStringBuilder();
    connBuilder.Database ="xxx";
    connBuilder.Password ="xxxx";
    connBuilder.PersistSecurityInfo = false;
    connBuilder.Server = "xxxxxx";
    connBuilder.UseCompression = true;
    connBuilder.UserID = "xxxxx";
 
    // Create the connection
    MySqlConnection conn = new MySqlConnection(connBuilder.ConnectionString);
    
   
 
 
 
        MySqlCommand comm = new MySqlCommand(cmdstring, conn);
 
//comm.Parameters.AddWithValue("userID", 2);
comm.Parameters.AddWithValue("selected", 1);
    //comm.Parameters.AddWithValue("userID", 2);
    //comm.Parameters.AddWithValue("selected", 1);
 
    try {
      // Open and execute the command
      conn.Open();
      
    
 
      using (MySqlDataReader reader = comm.ExecuteReader())
      {
          while (reader.Read())
          {
              string text = string.Format(
                "{0}\r\n{1}\r\n{2}\r\n{3}\r\n{4}\r\n{5}\r\n{6}",
                reader["fldContact"],
                reader["Name"],
                reader["ad1"],
                reader["ad2"],
                reader["ad3"],
                reader["ad4"],
                reader["ad5"]);
 
              Phrase myPhrase = new Phrase(text, times);
              
              table.AddCell(myPhrase);
              
          }
      }    
 
 
 
      doc.Add(table);
      doc.Close();
    } finally {
      conn.Dispose();
      comm.Dispose();
    } 
 
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
 
    }
}

Open in new window

Avatar of oobayly
oobayly
Flag of United Kingdom of Great Britain and Northern Ireland image

It's possible that an exception is being thrown in the try block at the end of the Page_Load event handler. If this is the case, doc.Close() is never called.
See what happens if you place doc.Close() in the finally block
    protected void Page_Load(object sender, EventArgs e)
    {
  
      // Snip
 
      try {
  
        // Snip
 
        doc.Add(table);
        // Remove here
        // doc.Close();
      } finally {
        // Close should always be called now
        doc.Close();
        conn.Dispose();
        comm.Dispose();
      } 
 
    }

Open in new window

Avatar of bogorman
bogorman

ASKER

Thanks for your quick reply.
When I try to build, I get the error:

"iTextSharp.text.document.close" is inaccessible due to its protection level

I have altered the final coding as you suggest. Think it's right:

 
      doc.Add(table);
      //doc.Close();
    } finally {
      doc.close();
      conn.Dispose();
      comm.Dispose();
    }
 
    }

In fact intellisense displays this error if I hover over the doc.Close(); line.
Make sure Close() has an uppercase C, the code you've posted is lowercase, which could be an overloaded public method (which would explain the compile error)
Yes, I noticed that after I send you the coding. Unfortunately, when I change the c to uppercase I still get the error via intellisense.
Any other ideas?
Any further thoughts on this. I assume the syntax has to be different if I move the Close statement.

One thing I thought of. If the page attempts to create the pdf where there is one already present, it has to overwrite the earlier copy. Should I delete this first? If so, how can I in c sharp detect whether the file is present and, if so, delete it?  Could this cause the corruption of the file?
Sorry. I may have made a mistake.
I now find it builds without error - maybe I forgot to save the project before building - but it still creates a zero-byte file which I cannot delete. Also, I get an error when opening the page:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.  

Stack Trace:

[IOException: The document has no pages.]
   iTextSharp.text.pdf.PdfPages.WritePageTree() +1287
   iTextSharp.text.pdf.PdfWriter.Close() +75
   iTextSharp.text.pdf.PdfDocument.Close() +138
   iTextSharp.text.Document.Close() +101
   _Default.Page_Load(Object sender, EventArgs e) +1118
   System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +14
   System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +35
   System.Web.UI.Control.OnLoad(EventArgs e) +99
   System.Web.UI.Control.LoadRecursive() +50
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +627

Would appreciate any further help you can give me on this.

 
Ah, what appears to be happening is that iTextSharp can't write an empty document (it doesn't conform to the PDF spec, as far as I remember), so it throws the exception when attempting to close the document (and the underlying stream).
Hence the underlying FileStream remains open, with a read lock (which is the default lock when no FileShare parameter is passed to the FileStream constructor) maintained by IIS.

So, first you need to fix the code creating the PDF. However, to prevent the locking issue, you need to make sure the underlying stream is closed, even if the PDF can't be created.
protected void Page_Load(object sender, EventArgs e)
{
  Document doc = new Document(PageSize.A4, 12, 15, 30, 0);
  string pdfFileLocation = @"D:\inetpub\DomainID54847\ConvertToPdf\Test.pdf";
  
  System.IO.FileStream docStream = new System.IO.FileStream(pdfFileLocation, System.IO.FileMode.Create);
  PdfWriter.GetInstance(doc, docStream);
 
  // Snip
 
  try {
 
    // Snip
 
    doc.Add(table);
    // Remove here
    // doc.Close();
  } finally {
    // Close should always be called now
    // If the PDF has no contents, an exception will not be caught,
    // but at least the file will be closed (and unlocked)
    try {
      doc.Close();
    } finally{
      // Also make sure the underlying stream is closed
      // You could delete the file here if it's empty
      docStream.Close();
    }
    
    conn.Dispose();
    comm.Dispose();
  } 
 
}

Open in new window

Thanks very much. Will try that.
Also, is it advisable to delete the 'old' pdf file first before recreating the new one? If so, can you suggest a coding to check if the file is present and, if so, delete it?
 
Deleting a file before it's created is an option, however it does mean that a valid file may be overwritten which may not be desired. Personally, I would only delete the file if the PDF generation fails.
As for determining if the file exists, you can use the FileInfo class, which not only will tell you if the file exists, but the size as well as various other information.
System.IO.FileInfo fi = new System.IO.FileInfo(fileName);
if (fi.Exists && fi.Length == 0){
  // File already exists, but is empty
  // Do stuff
}

Open in new window

Think you have solved my problem and the pdf does not seem to be being corrupted now.
However, I would like to have a button on the form on the webpage to open the pdf. At present, the html is listed below.

The ViewPrintPdf function is included in the cs file

 protected void ViewPrintPdf(object sender, EventArgs e)
    {
        Response.Redirect(pdfFileLocation, true);
    }

The problem is that both buttons close the form. I assume that this is due to the action of the form being set to "../Home.asp" and any both buttons act as submit buttons (or am I talking nonsense?)

What I would like to do is for btnViewPrintPdf to open the pdf in a popup window and the other button to redirect the page to the home page.

I realise this is another question, though related to my original one. If you would prefer me to assign the points to you at this stage and post another question, I will quite understand.
<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
 
                
 
    void Close(object sender, EventArgs e)
    {
        Response.BufferOutput = true;
        Response.Redirect("../Home.asp");
    }
 
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>LIFEnet Label Printing Utility</title>
</head>
<body>
    <form id="form1" runat="server" action="../Home.asp">
    <div>
        <br />
        <table bordercolor="#ffffff" style="width: 672px" align="center">
            <tr>
                <td colspan="3" style="font-weight: bold; font-size: 18pt; width: 696px; font-family: Arial; text-align: center">
                    Lifenet Label Printing Utility<br />
                    <br />
                </td>
            </tr>
            <tr>
                <td colspan="3" style="font-size: 20px; width: 696px; font-family: Arial; text-align: center">
                    Your labels are ready to print.<br />
                    <br />
                    <br />
                    <br />
                </td>
            </tr>
            <tr>
                <td colspan="3" style="width: 696px; text-align: center">
                    <asp:Button ID="btnViewPrintPdf" runat="server" onclick="ViewPrintPdf" Text="View/Print Your Labels" Height="40px" Width="304px" />&nbsp;
                    <asp:Button ID="btnClose"  runat="server" onclick="Close" Height="40px" Text="Close and Return to LIFEnet Homepage" Width="304px" /></td>
            </tr>
        </table>
        &nbsp;<br />
        <br />
        &nbsp;
        </div>
    </form>
</body>
</html>

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of oobayly
oobayly
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi oobayly,
Thanks so much for your help. Have been struggling to find the solution to the corruption of the pdf file for weeks. You further advice on the ViewPrint button is most helpful and works fine.
Regards
Brian