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.
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.