ASP.NET 2.0 Resources

Powered by Blogger

Filling and processing Adobe PDF Forms with iTextSharp

As i have blogged before, iTextSharp is a .NET port of iText Java-PDF library. Current version of iTextSharp 3.1.5 (2006-09-14) is based on iText 1.4.5 and among other cool things allows you to work with Adobe's FDF (Form Data Format) data. FDF is used by PDF forms created with the form tools in Adobe Acrobat. These PDF forms can be displayed and filled by users directly in web browser and then submited back to the server for further processing - for example flatten them to downloadable PDF file. This communication between client and server uses Adobe FDF protocol.

With iTextSharp you can easily create FDF file to prefill values in the form and also to process form submitted data back on the server. I am currently using this technique in one application, so i decided to drop a little demo project here. In this application advisors fill client's contract data directly to Adobe PDF form and submitting them back to system. The contract PDF form is prefilled with date, advisor's name, address and other data. When the advisor submits this form back to server, it's processed by ASP.NET, stored to database and finally flattened to classic downloadable PDF file.

This demo project is build for ASP.NET 2.0 and you will need iTextSharp library for it to work. It's made as simple as possible, and you can download PDFFormDemo.zip right now. It shows the basic steps to create FDF stream with prefilled data and to catch form submit back on the server.

The first file Default.aspx is used as a simple ASP.NET form to enter some user data (in this example it's first and last name). The form contains two simple text boxes and one submit button. Let's take a look at the submit button's onclick event...

   1:  private readonly string pdfFormFileName = "PDFForm.pdf";
   2:   
   3:  protected void OpenPDF_Click(object sender, EventArgs e)
   4:  {
   5:      Response.Clear();
   6:      Response.ContentType = "application/vnd.fdf";
   7:      
   8:      FdfWriter fdfWriter = new FdfWriter();
   9:   
  10:      fdfWriter.File = GetAbsolutePath() + pdfFormFileName;
  11:   
  12:      fdfWriter.SetFieldAsName("txtFirstName", FirstName.Text);
  13:      fdfWriter.SetFieldAsName("txtLastName", LastName.Text);
  14:   
  15:      Response.AddHeader("Content-disposition", "inline; filename=FlatPDFForm.fdf");        
  16:      fdfWriter.WriteTo(Response.OutputStream);        
  17:      Response.End();
  18:  }

This one is quite simple, we change response content type to application/vnd.fdf and use FdfWriter class to create FDF stream. First and Last name are stored to this stream (txtFirstName and txtLastName are the names of Adobe PDF Form fields). The File property of FdfWriter class associates this FDF file with our PDF form (PDFForm.pdf) and this file is opened in the client's browser (the path is abolute Url location to PDF file). We also set content disposition to open this file inline in the browser.

After this Onclick handler executes, PDF file opens in your browser. You now have prefilled values for FirstName and LastName in the PDF form and you can further change them so as to fill other form fields. After you click the form's submit button the data is submitted as FDF stream to the server (to the location hardcoded in PDF file - in our example ProcessFDF.ashx). The ProcessFDF.ashx file is HTTP handler file used to process the submitted data. Let's look at it's ProcessRequest method.

   1:  private readonly string pdfFormFileName = "PDFForm_print.pdf";    
   2:   
   3:  public void ProcessRequest (HttpContext context) {
   4:      string contentType = context.Request.ContentType;
   5:   
   6:      if (String.Compare(contentType, "application/vnd.fdf", true) == 0)
   7:      {
   8:          ProcessFDFRequestFlattenPDF(context);
   9:      }
  10:      else
  11:      {
  12:          context.Response.ContentType = "text/plain";
  13:          context.Response.Write("Not a FDF request");
  14:      }
  15:  }
  16:   
  17:  private void ProcessFDFRequestFlattenPDF(HttpContext context)
  18:  {
  19:      MemoryStream pdfFlat = new MemoryStream();
  20:   
  21:      PdfReader pdfReader = new PdfReader(context.Request.MapPath(pdfFormFileName));
  22:      PdfStamper pdfStamper = new PdfStamper(pdfReader, pdfFlat);
  23:   
  24:      // bind fields from fdf...
  25:      FdfReader fdfReader = new FdfReader(context.Request.InputStream);
  26:   
  27:      AcroFields pdfForm = pdfStamper.AcroFields;
  28:      pdfForm.SetFields(fdfReader);
  29:      
  30:      pdfStamper.FormFlattening = true;
  31:      pdfStamper.Writer.CloseStream = false;
  32:      pdfStamper.Close();
  33:   
  34:      context.Response.ContentType = "application/pdf";
  35:      context.Response.AddHeader("Content-disposition", "attachment; filename=FlatPDFForm.pdf");
  36:      
  37:      pdfFlat.WriteTo(context.Response.OutputStream);
  38:      pdfFlat.Close();
  39:  }

The ProcessRequest method tests for correct submited content type and if it's set correctly to application/vnd.fdf processes this request. We just flatten this FDF stream with our PDF form file and send the flattened PDF file back to the client's browser. You may have noticed we are using another PDF form file here PDFForm_print.pdf. This file is basically the same as the one in previous step, only without TextBox black borders. The logic is quite straightforward, PdfReader is used to read PDF form file and PdfStamper created for this file. The stampers FormField collection is set to our submited FDF request and finally the PDF Form and FDF stream are merged together in one noneditable PDF file. This file is streamed back to the client's browser this time as an attachment.

As you can see this is quite easy. This technique works fine with Adobe Acrobat generated PDF Forms. There are some problems with iTextSharp and Adobe Distiler forms, because it generates some weird tags. This was only a demo, so please keep in mind real world application would need some additional erro checking and stuff like that.

107 comments

CSS Friendly Toolkit: GridView - Visible property and other problems

New version of CSS Friendly Control Adapter Toolkit finally brought support for GridView control. The CSS Friendly Control Adapter Tookit hooks to ASP.NET processing pipeline in the stage where the control markup is generated. It's purpose it to emit clear CSS stylable html markup. The code generated by the toolkit is quite clean, but currently i'm facing some problems with GridView adapter. This aplies to beta 2.0 version.

The first problem (which is currently discussed at ASP.NET Forums) is with Visible property. This adapter ignores the Visible property and GridView columns are rendered even if set to false. There is a bug fix posted on ASP.NET forum where the WriteRows method of GridViewAdapter class is slightly altered to support this property setting. This fix is working, but was working only to a point for me. It does not correctly support setting Visible property for individual cells. A little bit of digging with Reflector showed how it's handled by GridView's Render method and this fix is included in code sample later.

The second problem i noticed was the lack of support for individual Column styling. For example i wanted to align all number columns to the right. GridView's BoundField and other Column fields have ItemStyle-CssClass property for this purpose. But this property is ignored by CSS Control Adapter and makes it impossible to style individual Columns. I'm also including the fix for this "bug"/"feature".

To fix these two problems replace foreach TableCell loop in WriteRows method of GridViewAdapter class (GridViewAdapter.cs) with the following code...

   1:  private void WriteRows(HtmlTextWriter writer, GridView gridView, GridViewRowCollection rows, string tableSection)
   2:  {
   3:      ...
   4:      foreach (TableCell cell in row.Cells)
   5:      {
   6:          DataControlFieldCell controlCell = cell as DataControlFieldCell;
   7:          if (controlCell != null)
   8:          {
   9:              DataControlField controlField = controlCell.ContainingField;
  10:              if (controlField != null)
  11:              {
  12:                  if (!controlField.Visible)
  13:                  {
  14:                      cell.Visible = false;
  15:                  }
  16:                  // copy item CSS class
  17:                  cell.CssClass = controlField.ItemStyle.CssClass;
  18:              }
  19:          }
  20:          writer.WriteLine();
  21:          cell.RenderControl(writer);
  22:      }
  23:      ...
  24:  }

33 comments

Created dolly