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.
- Open OpenOffice Draw, create your form. You can also import an existing PDF using the PDF Import plugin.
- If you don’t see the Form Controls toolbar, add it by going to View --> Tool Bars --> Form Controls.
- For each field you want to populate in the form,
- Select the
TextBox
button on the Form Controls toolbar and draw it on your form.
- Select that
textbox
and press the Control button on the Form Controls toolbar. Format the textbox
:
- Change the Border to “Without frame” so it blends into the form when generated
- Change the name of
textbox
to something meaningful
- Change the alignment to centered
- Adjust the font size, weight, etc.
- Save the file and export it to PDF by going to File --> Export As PDF.
- Be sure the “Create PDF form” general option is selected and “Submit format” is set to PDF.
- Refresh your Forms folder and add both files to your project.
- 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
textbox
es 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()
{
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.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MyFormLetter(Applicant applicant)
{
try
{
applicant.Company = "My Company";
Dictionary<string,> formFields = new Dictionary<string,>
{
{"ApplicantName", applicant.Name},
{"CompanyName", applicant.Company}
};
string fileName = "MyFormLetter.pdf";
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
.
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);
MemoryStream returnStream = new MemoryStream();
returnStream.Write(memoryStream.GetBuffer(), 0,
memoryStream.GetBuffer().Length);
returnStream.Flush();
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;
foreach (KeyValuePair<string,> formField in formFields)
{
fields.SetField(formField.Key, formField.Value);
}
stamper.FormFlattening = true;
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)
{
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() %>';
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