Import Form Data
Filled PDF forms carry data: the customer's name on an order, the check mark on a consent form, the date of an insurance claim. Forms.GetAcroFormFields reads this data from a PDF file or a byte array, without opening the document in an editor. Your code turns the values into JSON or a C# model, accepts forms uploaded to an ASP.NET Core application and transfers the values into a TX Text Control form template.
A first loop over the text fields and check boxes of a PDF form is part of Read Form Field Values in Getting Started. The classes the method returns are described in AcroForms. The examples read Documents/order-form-filled.pdf, the order form of Form Fields after a customer filled it in.
How GetAcroFormFields Reads a Form
Forms.GetAcroFormFields is a static method with two overloads: one takes the path of a PDF file, the other the PDF as a byte array. It returns an array of FormField objects, each of the class that fits its field, and your code reads the value from the property of that class:
| Class | Value | What to expect |
|---|---|---|
| FormTextField | Value | The entered text. A date form field of TX Text Control is a text field in the PDF, so its value is the date as the form displayed it. |
| FormCheckBox | IsChecked | true for a checked box |
| FormRadioButton | IsChecked | No member tells which option of a radio group is selected. |
| FormComboBox, FormListBox | Value | The selected item, or the typed text of an editable combo box. Options lists the items. |
| FormButton | None | A push button has no value. |
Plan for these details when you process forms from different sources:
- Names identify the values. FieldName is the name the form's author gave the field. A name can occur more than once in the array, for example for a field that appears in several places, so store values with the indexer of a dictionary instead of
ToDictionary, which throws for duplicate keys. - Empty fields.
string.IsNullOrWhiteSpacecatches a text field that the user left blank before its value reaches your data model. - Derived classes first.
FormRadioButtonderives fromFormCheckBoxandFormListBoxfromFormComboBox, so aswitchmust test the derived class first to tell them apart. - Signature fields aren't part of the array. They are the subject of Signature Fields.
GetAcroFormFields reads forms that users filled in and saved with Adobe Acrobat Reader, with Google Chrome since version 85 or with the Document Viewer.
Map the Values to JSON and a Model
The following Program.cs reads the order form, collects the values by field name, prints them as JSON and fills a C# class with them:
using System.Globalization;
using System.Text.Json;
using TXTextControl.DocumentServer.PDF;
using TXTextControl.DocumentServer.PDF.AcroForms;
FormField[] fields = Forms.GetAcroFormFields("Documents/order-form-filled.pdf");
if (fields.Length == 0)
{
Console.WriteLine("The PDF document has no form fields.");
}
// One entry per name; a name that occurs more than once keeps its last value
Dictionary<string, object?> values = new Dictionary<string, object?>();
foreach (FormField field in fields)
{
switch (field)
{
case FormTextField textField:
values[field.FieldName] = string.IsNullOrWhiteSpace(textField.Value) ? null : textField.Value;
break;
case FormCheckBox checkBox: // check boxes and radio buttons
values[field.FieldName] = checkBox.IsChecked;
break;
case FormChoiceField choiceField: // combo boxes and list boxes
values[field.FieldName] = string.IsNullOrEmpty(choiceField.Value) ? null : choiceField.Value;
break;
}
}
Console.WriteLine(JsonSerializer.Serialize(values, new JsonSerializerOptions { WriteIndented = true }));
OrderForm order = new OrderForm
{
Customer = values.GetValueOrDefault("Customer") as string,
Product = values.GetValueOrDefault("Product") as string,
// Parse the date with the culture the form displays it in
InvoiceDate = DateTime.TryParse(values.GetValueOrDefault("InvoiceDate") as string,
CultureInfo.GetCultureInfo("en-US"), DateTimeStyles.None, out DateTime date) ? date : null,
TermsAccepted = values.GetValueOrDefault("TermsAccepted") is true
};
Console.WriteLine($"{order.Customer} ordered {order.Product} on {order.InvoiceDate:yyyy-MM-dd}.");
public class OrderForm
{
public string? Customer { get; set; }
public string? Product { get; set; }
public DateTime? InvoiceDate { get; set; }
public bool TermsAccepted { get; set; }
}
The code works as follows:
GetAcroFormFieldsreads the file directly. You don't create aServerTextControlfor it.- The
switchstatement uses the base class FormChoiceField for combo boxes and list boxes, andFormCheckBoxfor check boxes and radio buttons, because the program only needs their values. - Serializing the
FormFieldarray itself doesn't help:JsonSerializerwrites only the properties of the base class, without the values. Map each type to its value, as theswitchstatement does. - The dictionary becomes the JSON object, and the
OrderFormclass gives the rest of your application typed values.
Accept Uploaded Forms in ASP.NET Core
A web application receives filled forms as uploads. The following controller action reads the uploaded PDF from a byte array and returns its values as JSON:
using Microsoft.AspNetCore.Mvc;
using TXTextControl.DocumentServer.PDF;
using TXTextControl.DocumentServer.PDF.AcroForms;
public class FormDataController : Controller
{
[]
public IActionResult Import(IFormFile document)
{
if (document == null || document.Length == 0)
{
return BadRequest("Upload a filled PDF form.");
}
// Copy the upload into a byte array
byte[] data;
using (MemoryStream memoryStream = new MemoryStream())
{
document.CopyTo(memoryStream);
data = memoryStream.ToArray();
}
FormField[] fields;
try
{
fields = Forms.GetAcroFormFields(data);
}
catch (TXTextControl.FilterException exception)
{
return BadRequest($"The PDF form could not be read: {exception.Reason}");
}
Dictionary<string, object?> values = new Dictionary<string, object?>();
foreach (FormField field in fields)
{
switch (field)
{
case FormTextField textField:
values[field.FieldName] = textField.Value;
break;
case FormCheckBox checkBox:
values[field.FieldName] = checkBox.IsChecked;
break;
case FormChoiceField choiceField:
values[field.FieldName] = choiceField.Value;
break;
}
}
return Ok(values);
}
}
The code works as follows:
- The upload is copied into a byte array for the second overload of
GetAcroFormFields, as Load Uploaded Documents in ASP.NET Core does for other documents. - A file that can't be read throws a FilterException. Its Reason is, for example,
WrongFileTypefor a file that isn't a PDF orInvalidPasswordfor a PDF that requires a password to open.GetAcroFormFieldstakes no password, so such forms can't be read. - The file doesn't import the
TXTextControlnamespace, because both namespaces contain a class namedFormField.FilterExceptionis written in full instead.
Transfer the Values Into a Form Template
TX Text Control doesn't write values into an existing PDF: the Forms class only reads. To create a new PDF with the values, for example a corrected or completed version of the form, transfer them into your template of the form and save it. The following program fills Documents/order-form.tx with the values of the filled PDF:
using System.Globalization;
using TXTextControl;
using TXTextControl.DocumentServer.PDF;
using AcroForms = TXTextControl.DocumentServer.PDF.AcroForms;
// The fields of the filled PDF by name
Dictionary<string, AcroForms.FormField> pdfFields = new Dictionary<string, AcroForms.FormField>();
foreach (AcroForms.FormField pdfField in Forms.GetAcroFormFields("Documents/order-form-filled.pdf"))
{
pdfFields[pdfField.FieldName] = pdfField;
}
using (ServerTextControl tx = new ServerTextControl())
{
tx.Create();
tx.Load("Documents/order-form.tx", StreamType.InternalUnicodeFormat);
// The main text, text frames, headers and footers
foreach (IFormattedText textPart in tx.TextParts)
{
foreach (FormField formField in textPart.FormFields)
{
if (string.IsNullOrEmpty(formField.Name) ||
!pdfFields.TryGetValue(formField.Name, out AcroForms.FormField? pdfField))
{
continue;
}
switch (formField)
{
case TextFormField textFormField when pdfField is AcroForms.FormTextField textField:
if (!string.IsNullOrWhiteSpace(textField.Value))
{
textFormField.Text = textField.Value;
}
break;
case CheckFormField checkFormField when pdfField is AcroForms.FormCheckBox checkBox:
checkFormField.Checked = checkBox.IsChecked;
break;
case SelectionFormField selectionFormField when pdfField is AcroForms.FormChoiceField choiceField:
// -1 selects nothing if the value isn't one of the items
selectionFormField.SelectedIndex = Array.IndexOf(selectionFormField.Items, choiceField.Value);
break;
case DateFormField dateFormField when pdfField is AcroForms.FormTextField dateText:
// In the PDF, a date form field is a text field
dateFormField.Date = DateTime.TryParse(dateText.Value, CultureInfo.GetCultureInfo("en-US"),
DateTimeStyles.None, out DateTime date) ? date : null;
break;
}
}
}
tx.Save("Documents/order-form-imported.pdf", StreamType.AdobePDF);
}
The code works as follows:
- The alias
AcroFormsseparates the classes of the PDF from the form fields of the document, which live in theTXTextControlnamespace. - ServerTextControl.TextParts covers the form fields in headers, footers and text frames, too.
- Each form field type receives its value through its own property: Text for text boxes, Checked for check boxes, SelectedIndex for selection fields and Date for date fields.
- Saved with
StreamType.AdobePDF, the template becomes a fillable PDF with the imported values. To archive the values as fixed text, flatten the fields before saving, as Flatten Forms describes.
Instead of setting each field, you can also merge the values with MailMerge and FormFieldMergeType.Preselect, as Merge Form Fields shows. The blog article Create, Pre-Select, Flatten and Extract PDF Form Fields using C# combines creating, filling and reading a form in one example.
Next Steps
- Export XFDF: store the imported values as XFDF.
- Flatten Forms: turn the filled form into a document with fixed values.
- AcroForms: the classes of PDF form fields and how forms are created.
- Form Fields in Document Viewer: let users fill out forms in your application.