Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Generate Popup PDF Forms with ASP.NET MVC and Open Office

0.00/5 (No votes)
9 Jun 2010 1  
Use Open Office and Sun PDF Import plugin to create PDF forms and display with ASP.NET MVC

Introduction

This demo shows you how to generate and populate a popup PDF form in ASP.NET MVC. The generated PDF form is opened in a new window, but errors are shown in the parent window. The demo also shows you how to create and generate PDF forms using free open source tools such as Open Office 3.2 and iTextSharp, therefore you do not need non-free tools such as Acrobat Pro.

Setup your Environment

If you don't have Acrobat Pro to create PDF forms, this demo shows you how to use Open Office 3.2 and the Sun PDF Import extension.

Included in this Demo

The demo project includes the free open source DLL for iTextSharp 5.0.2.0 and the JS files for jquery 1.3.2 and the query plugin.

Create the PDF Form using OpenOffice

If you don’t have Acrobat Pro to create PDF forms, you can use the free open source Open Office and PDF Import plugin.

  1. Open OpenOffice Draw, create your form. You can also import an existing PDF using the PDF Import plugin.
  2. If you don’t see the Form Controls toolbar, add it by going to View --> Tool Bars --> Form Controls.
  3. For each field you want to populate in the form,
    1. Select the TextBox button on the Form Controls toolbar and draw it on your form.
    2. Select that textbox and press the Control button on the Form Controls toolbar. Format the textbox:
      1. Change the Border to “Without frame” so it blends into the form when generated
      2. Change the name of textbox to something meaningful
      3. Change the alignment to centered
      4. Adjust the font size, weight, etc.
  4. Save the file and export it to PDF by going to File --> Export As PDF.
    1. Be sure the “Create PDF form” general option is selected and “Submit format” is set to PDF.
  5. Refresh your Forms folder and add both files to your project.
  6. You’ll need to keep the ODG file if you want to make changes to the form later because the PDF you export loses the textboxes when you import it back into Open Office. If you open the ODG file later to make changes, be sure to press the Design Mode On/Off button on the Form Controls toolbar to see and select the textboxes in your layout.

Create Controller GET Action

In HomeController, you will see the get and post actions for MyFormLetter. In the get action, you will see a default business object is created and passed to the view.

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult MyFormLetter()
{
            // by setting the Company property on the model, 
            // the form will be filled out with the company name,
            // but not the applicant name
            Applicant defaultApplicant = new Applicant()
                                             {
                                                 Company = "My Company"
                                             };
            return View(defaultApplicant);
}

Create the View

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
    Inherits="System.Web.Mvc.ViewPage" %>

The form action specifies the post action and opens the PDF in a new browser window.

<form action="<%= Url.Action("MyFormLetter", "Home") %>" method="post"
        target="_blank">

Input fields are pre-populated with the business object properties set in the get action.

<input id="Company" name="Company" value="<%= Model.Company %>" /> 

Create Controller Post Action

The MyFormLetter view posts to the MyFormLetter post action, passing the user-populated business object. The business object is modified by any business logic and the PDF form fields are populated. The PDF file is loaded, generated and populated by the GetPdfFileStreamResult method. If an error occurs, the error is sent back to the parent page and the popup form is closed.

/// <summary>
/// The MyFormLetter form posts to this action
/// </summary>
/// <param name="applicant"></param>
/// <returns></returns>
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MyFormLetter(Applicant applicant)
{
            try
            {
                // uncomment this to test error handling
                // throw new NullReferenceException("OH NOOOOOO");

                // this is where you would get further info by calling business logic, 
                // data access, etc.
                applicant.Company = "My Company";

                // populate the value of each form field in the pdf form
                Dictionary<string,> formFields = new Dictionary<string,>
                                       {
                                            {"ApplicantName", applicant.Name},
                                            {"CompanyName", applicant.Company}
                                       };
                string fileName = "MyFormLetter.pdf";

                // we don't want the user to see "MyFormLetter.pdf" 
                // when they save the file, 
                // we want them to see "YourFormLetter.pdf" when they save the PDF
                // so we alias the name simply by passing it to another action 
                // named YourFormLetter

                // pass the file stream result to the alias action
                TempData["MyFormLetter_FileStreamResult"] = 
			GetPdfFileStreamResult(fileName, formFields);
                return RedirectToAction("YourFormLetter");
            }
            catch (Exception ex)
            {
                return HandleErrorForPopup(ex, applicant);
            }
}  

Alias the Form Name

When the user saves the PDF document, the default name of the file will be set to the controller action name. If we don’t want the user to see “MyFormLetter.pdf” and instead want them to see “YourFormLetter.pdf”, we can simply pass the FileStreamResult to a new controller action called YourFormLetter.
/// <summary>
/// When the user saves the pdf, the default name of the 
/// file will be "YourFormLetter.pdf".
/// </summary>
/// <returns></returns>
public FileStreamResult YourFormLetter()
{
            return (FileStreamResult) TempData["MyFormLetter_FileStreamResult"];
} 

Generate and Populate the PDF Form

The GetPdfFileStreamResult and GeneratePdf private methods load the PDF form file from the server, and populate the form fields using iTextSharp.

private FileStreamResult GetPdfFileStreamResult
	(string fileName, Dictionary<string,> formFields)
{
            MemoryStream memoryStream = GeneratePdf(fileName, formFields);

            // create a new return stream because the 
            // MemoryStream from the file is closed
            MemoryStream returnStream = new MemoryStream();
            returnStream.Write(memoryStream.GetBuffer(), 0, 
			memoryStream.GetBuffer().Length);
            returnStream.Flush();
            // rewind stream back to beginning so it can be rendered to the page
            returnStream.Seek(0, SeekOrigin.Begin);

            return new FileStreamResult(returnStream, "application/pdf");
}

private MemoryStream GeneratePdf(string fileName, Dictionary<string,> formFields)
{
            string formFile = HttpContext.Server.MapPath("~/Forms/" + fileName);
            PdfReader reader = new PdfReader(formFile);
            MemoryStream memoryStream = new MemoryStream();
            PdfStamper stamper = new PdfStamper(reader, memoryStream);
            AcroFields fields = stamper.AcroFields;

            // set form fields
            foreach (KeyValuePair<string,> formField in formFields)
            {
                fields.SetField(formField.Key, formField.Value);
            }

            stamper.FormFlattening = true;
            // release file
            stamper.Close();
            reader.Close();

            return memoryStream;
}

Sending Errors Back to the Parent Window

Since we don’t want errors to appear in the popup window, we want them to appear in the parent window, then close the popup. Errors are passed to TempData and the ParentError action is called.

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult HandleErrorForPopup(Exception ex, object inputData)
{
            //TODO: log and/or email error


            // close the popup browser window and display the error in the parent window
            TempData["ErrorMessage"] = ex.Message;
            return RedirectToAction("ParentError");
}

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult ParentError()
{
            return View();
}

The ParentError.aspx view uses JavaScript to pass errors back to the parent window and closes the popup. If you just call window.close, Internet Explorer will prompt you asking if you want to allow the page to close. With the code below, the page will close without a prompt in both Internet Explorer and Firefox.

function HandleError() {
            opener.window.location =
                '/Public/Error.html?error=<%= TempData["ErrorMessage"].ToString() %>';

            // close the form window without a prompt
            window.open('', '_self');
            window.close();
} 

Notice that the Error.html page lives in the Public folder, not in any of the Views folder. This is because it is designed to handle errors that occur anywhere in the application, including login errors, so an error will be displayed even if the user is not authorized.

Errors are Displayed to the User using JQuery

<script src="/Content/jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="/Content/jquery.query.js" type="text/javascript"></script>
<script type="text/javascript">
    $(document).ready(function() {
        $("div#lblErrorMessage").html($.query.get("error"))
    });
</script>

Try It Out

Hit Ctrl-F5 and see the My Form Letter page. Type in a name, submit the form, and see the PDF popup in a new browser window. To test error handling, uncomment the throw new NullReferenceException("OH NOOOOOO") line in the MyFormLetter controller action, hit Ctrl-F5, and press submit.

History

  • 9th June, 2010: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here