tirsdag den 10. juli 2012

Editing PDF form on the web

I was asked to make a PDF form editable on the web. The HR department already had the form, now they wanted me to make it editable via an url.

The solution involves

- setting up a database to hold the PDF form data,
- making the PDF form submittable to a web page,
- utilizing the iTextSharp-library to parse the submitted PDF form

Setting up the database involves creating a table with columns for every field in the PDF-form you need stored - likely all of them. Also there must be a primary key in the form of an auto-incremented value or a global unique identifier. Having set up the database, allow the PDF form to be submittable to a web page. In Adobe Acrobat Pro, go to the "Forms -> Add or Edit Fields" and add a PDF button with the text "save" on it. Define a button action for the mouse-up event; specifically, a "submit form" action (*). The properties for the action should look akin to this:

*) I'm using a Danish version of Acrobat Pro, so the menus and texts may slightly different from the above.

I.e. the form submits into a generic handler "pdfHandler.ashx" when it is clicked (the mouse-down action). The handler's default "ProcessRequest" function looks like this:


 public void ProcessRequest(HttpContext context)

            HttpRequest pdfRequest = HttpContext.Current.Request;
            System.IO.Stream istream = HttpContext.Current.Request.InputStream;
            FdfReader fdf = new FdfReader(istream);

            // eksisterende?
            if (!string.IsNullOrEmpty(fdf.GetFieldValue("ident")))
                DataClassesDataContext dbContext = new DataClassesDataContext();

                int id = Convert.ToInt32(fdf.GetFieldValue("ident"));
                lejere lejer = dbContext.lejeres.Single(foo => foo.id.Equals(id));
                lejer.ad1 = fdf.GetFieldValue("ad1") == null ? string.Empty : fdf.GetFieldValue("ad1").ToString();
                // populate all database object properties
                #region new lejer

                    DataClassesDataContext dbContext = new DataClassesDataContext();
                    lejere lejer = new lejere()
                        ad1 = fdf.GetFieldValue("ad1") == null ? string.Empty : fdf.GetFieldValue("ad1").ToString(),
                        ad2 = fdf.GetFieldValue("ad2") == null ? string.Empty : fdf.GetFieldValue("ad2").ToString(),
                        // populate all database object properties

                    if (string.IsNullOrEmpty(fdf.GetFieldValue("lejernavn1")) && string.IsNullOrEmpty(fdf.GetFieldValue("lejernavn2")))
                        lejer.lejernavn1 = "Uudfyldt";
                        lejer.lejernavn2 = "Uudfyldt";
                catch (Exception ex)
                    throw ex;


            HttpContext.Current.Response.Redirect("Default.aspx", true);

Basically what takes place here, is that an iTextSharp pdf-reader is instantiated from a HTTPRequest which is the submitted PDF document. Using LINQ to SQL, a database object corresponding to the PDF form (recall we just created the database and its table with columns for all the PDF form fields) is populated from the PDF form (the 'GetFieldValue' function of the iTextSharp library) and stored to the database via LINQ to SQL. 

The "New" option is relatively straight forward, in as much as the LINQ to SQL functionality itself works out to insert a new auto incremented integer primary key, if you're - as am I in this case - going with auto-integers for database keys. The "Edit" option is more of a challenge - here we'll have to inject the primary key into the PDF form, before allowing the generic handler to take over and grap this value when the form is submitted. The way to do this is so:

In the edit-button click function, do this:


 int lejerId = Convert.ToInt32(LejereDropDown.SelectedValue);
                DAL.lejere lejer = dbContext.lejeres.Single(foo => foo.id.Equals(lejerId));

                PdfReader reader = null;
                AcroFields af = null;

                PdfStamper ps = GetPDFFields(ref reader, ref af);

                SetFormData(ref af, lejer);

                // forget to close() PdfStamper, you end up with
                // a corrupted file!
                if (reader != null)
                    Response.AddHeader("Content-Disposition", "inline;filename=lejekontrakt.pdf");
                    Response.ContentType = "application/pdf";

What happens here is that we instantiate an iTextSharp PDF-reader and use the "GETPDFField"-method to achieve a reference to all the form fields in this PDF. THe "SetFormData" function simply fills in these form fields (like so;  "af.SetField("ident", lejer.id.ToString());") - including a hidden "ident" field on the form. Once the form fields have been populated, we'll redirect into the form by changing the page headers and response contenttype, as written.

Hope the above helps! All the references to 'fields', 'forms' and such can get confusing, so be sure to get in touch with me if anything needs explaining further.

tirsdag den 3. juli 2012

Unsafe code in App_Code folder

I ran into an issue where my Visual Studio 2010 Professional reported "CS0227: Unsafe code may only appear if compiling with /unsafe".

But I had already checked the appropriate checkbox in the solution properties dialog!

Turns out the problem was having the unsafe code reside in the App_Code folder. Moving it to a different location solved the problem.