Mahendra Mavani's # Corner

My experiments with software development
posts - 35, comments - 26, trackbacks - 7

Wednesday, February 10, 2010

PDF Form Generation using iTextSharp

Forms are one of the very common requirement, especially if you are dealing with any government body. With aspiration towards automation, everybody seems to be moving towards PDF Forms which you can print but doesn't let you save soft copy of it.

On my current project at Headspring, we came across such requirement. System under development deals with multiple organizational bodies. Part of the integration effort needed submitting forms which are in predefined format. Most of the information that needed to be filled in has already been captured by the system. Hence in order to reduce manual work burden and thereby avoiding potential human error (either by typo or due to repeated work burden), it is required to be filled in automatically at the time of generation. In other work, as user to the this system, I want to pick certain record and click on link to generate particular form which is pre-populated with values for current record. Again remember, the original source here is PDF Forms as defined out side the system by third party.

Since open source is heart of our core development effort at Headspring, our first and very natural reaction was to look for any existing open source solution. Our lead architect Kevin Hurwitz has mastery in this area. Given any problem of such nature, Kevin can either elegant open source solution or come up with one, in almost no time (Some day I would like to steal this skill from Kevin).

In this case Kevin has come up with “iTextSharp” library. Little bit experimenting with it here and there, and we concluded that it will meet our requirement perfectly fine. Ours being ASP.NET MVC application, we decided to extend ActionResult which can be hooked up with any application. Here is our approach:

Step one is, ofcourse, download iTextSharp library and take dependency on it. Next is following interface

   1:  public interface IFormBinder<TFormModel>
   2:  {
   3:          byte[] Bind(TFormModel model, string pdfFormTemplateFullPath);
   4:  }

 

This is very simple (generic) interface with just one method. Generic type defined here is Model for any given PDF form [Yes, like all our view page, here to we opted to go with one Model per Form convention.. very soon, we will understand, why]. Next we have abstract implementation for this interface, which will lift most of the burden of filling values to given PDF form. Here is the code for that class:

   1: public abstract class BaseFormBinder<TReportModel> : IFormBinder<TReportModel>
   2:     {
   3:         public virtual void BindFormFields(TReportModel reportModel, AcroFields acroFields) { }
   4:  
   5:         public byte[] Bind(TReportModel reportModel, string pdfTemplateFullPath)
   6:         {
   7:             var memoryStream = new MemoryStream();
   8:  
   9:             PdfStamper pdfStamper = null;
  10:             try
  11:             {
  12:                 var pdfReader = new PdfReader(new RandomAccessFileOrArray(pdfTemplateFullPath), null);
  13:  
  14:                 pdfStamper = new PdfStamper(pdfReader, memoryStream);
  15:  
  16:                 var acroFields = pdfStamper.AcroFields;
  17:  
  18:                 BindFormFields(reportModel, acroFields);
  19:  
  20:                 pdfStamper.FormFlattening = true;
  21:  
  22:                 pdfStamper.Close();
  23:             }
  24:             finally
  25:             {
  26:                 if (pdfStamper != null) pdfStamper.Close();
  27:             }
  28:  
  29:             return memoryStream.ToArray();
  30:         }
  31:     }

This base class reads source PDF form given path and uses iTextSharp library to fill in data to this form and finally returns filled pdf form as byte array.

Next step is creation of custom ActionResult as follow:

   1:  public class PdfFormResult<TFormModel> : ActionResult
   2:      {
   3:          public PdfFormResult(TFormModel model, string formTemplate)
   4:          {
   5:              Model = model;
   6:              FormTemplate = formTemplate;
   7:          }
   8:   
   9:          public TFormModel Model { get; private set; }
  10:          public string FormTemplate { get; set; }
  11:   
  12:   
  13:          public override void ExecuteResult(ControllerContext context)
  14:          {
  15:              var pdfFormTemplateFullPath = HttpContext.Current.Server.MapPath("/PdfForms/" + FormTemplate);
  16:   
  17:              var formBinder = ObjectFactory.GetInstance<IFormBinder<TFormModel>>();
  18:   
  19:              var content = formBinder.Bind(Model, pdfFormTemplateFullPath);
  20:   
  21:              var result = new FileContentResult(content, "binary/octet-stream");
  22:              context.HttpContext.Response.AddHeader("content-disposition", string.Format("attachment; filename={0}", FormTemplate));
  23:   
  24:              result.ExecuteResult(context);
  25:          }
  26:      }

 

This is yet another generic class, which takes report model as it’s type. Main point worth highlighting here is line 17, which uses IoC container to resolve instance of IFormBinder. To me, this is classic example of leveraging IoC to it’s full capacity and simplifying your code. For my case, IoC is StructureMap and all I have to do to configure it for above need is, define following Structuremap registry: [I defer any further explanation about this to my buddy, Jimmy]

   1:  public class MyRegistry : Registry
   2:      {
   3:          public MyRegistry()
   4:          {
   5:   
   6:              Scan(cfg =>
   7:                       {
   8:                           cfg.Assembly("PdfFormGenerator");
   9:                           cfg.With<DefaultConventionScanner>();
  10:                           cfg.ConnectImplementationsToTypesClosing(typeof(IFormBinder<>));
  11:                       });
  12:          }
  13:      }

 

Once all these in place, for any new pdf form, all I have to do now  is

1) Define Report Model

2) In my controller write action that returns PdfFormResult<ReportModel>

3) Define FormBinder that extends BaseFormBinder<GivenReportModel>  

 

For Completion here is code for all three steps above, for a sample 2 field pdf form

Report Model:

   1:  public class SampleFormModel
   2:      {
   3:          public string Name { get; set; }
   4:          public string Gender { get; set; }
   5:      }

 

Controller Action:

   1:   public PdfFormResult<SampleFormModel> PdfForm()
   2:          {
   3:              var model = UseSomeServiceToGetThisData();
   4:              return new PdfFormResult<SampleFormModel>(model, "SampleForm.pdf");
   5:          }

 

FormBinder:

   1:      public class SampleFormBinder : BaseFormBinder<SampleFormModel>
   2:      {
   3:          public override void BindFormFields(SampleFormModel reportModel, AcroFields acroFields)
   4:          {
   5:              acroFields.SetField("Name", reportModel.Name);
   6:              acroFields.SetField("GenderMale", reportModel.Gender);
   7:              acroFields.SetField("GenderFemale", reportModel.Gender);
   8:          }
   9:      }

 

That’s it. Yes..you are done. Don’t believe me? Here is the sample app, download and play with it yourself .

Till next time…Develop smartly…

Technorati Tags:

posted @ Wednesday, February 10, 2010 10:39 PM | Feedback (0) |

Powered by: