提交和处理 PDF 表单数据
本文解释了 PDF 表单的基础知识。特别是,它提供了在 ASP.NET MVC 应用程序中处理 PDF 表单数据的示例代码。
如果您必须在 HTML 表单和 PDF 表单之间进行选择 - 或者可能需要同时支持两者 - 那么了解这两种表单之间的区别以及它们的共同之处是很有好处的。本文的范围仅限于经典的 PDF 表单 - 而不是 XFA 表单。
HTML 表单
HTML 表单看起来像这样
<h1>I want pizza!</h1>
<form method="get" action="order">
<p>Choose a size:</p>
<input type="radio" name="size" value="small">small<br>
<input type="radio" name="size" value="medium">medium<br>
<input type="radio" name="size" value="large">large
<p>Choose ingredients:</p>
<input type="checkbox" name="tomatoes" value="tomatoes">tomatoes<br>
<input type="checkbox" name="onions" value="onions">onions<br>
<input type="checkbox" name="tuna" value="tuna">tuna<br>
<input type="checkbox" name="cheese" value="cheese">cheese
<p>My name:</p>
<input type="text" name="name" /><input type="submit" value="order" />
</form>
字段本身及其外观都由同一个元素表示,即 input 元素。另一方面,PDF 完全分离了字段与其表示的概念。
PDF 表单
PDF 字段在文档级别定义,每个字段可以有零个或多个称为 _小部件_ (widgets) 的视觉表示。每个小部件都与一个页面相关联。下图显示了这种结构以及文档、页面、字段和小部件之间的关系。
在本文中,我将使用以下 PDF 表单。
我使用记事本创建了文本部分,然后将其打印成 PDF。接下来,我使用 Adobe Acrobat Pro DC 添加了表单元素。大小选项是共享同一组名称“size”的单选按钮。“small”、“medium”和“large”单选按钮是同一组名为“size”的组成部分。配料是带有相应名称的复选框。最后,有一个名为“name”的文本框和一个名为“order”的按钮。
向 PDF 添加提交按钮
要将 PDF 表单提交到 Web 端点,您需要添加一个具有 _提交表单操作_ 的按钮。您通常在 Adobe Acrobat 中进行此操作。这是添加提交表单操作后按钮属性对话框的操作选项卡的外观。
如果您选择该操作并单击“编辑”按钮,您将看到提交表单数据可用选项。
所选导出格式为 HTML。单击按钮时,这将把所有表单数据 POST 到指定的 URL。请注意,与 HTML 表单不同,无法指定 GET 作为 HTTP 方法。稍后我们将看到如何在 ASP.NET MVC 应用程序中处理此请求。
打开 PDF 表单
打开 PDF 表单似乎很简单,但事实并非如此。让我们在浏览器中打开此表单,看看会发生什么。您可以在此处打开它: http://www.tallcomponents.com/demos/pizza/form。
作为实现说明,该表单位于 MVC 应用程序的 Content 文件夹内,其操作方法如下。
public class PizzaController : Controller
{
public ActionResult Form()
{
return File("~/Content/order-pizza.pdf", "application/pdf");
}
}
您的浏览器很有可能会直接渲染 PDF 表单本身,而不是使用 Adobe Reader 插件。Google Chrome 将 PDF 渲染为 HTML,并破坏了大量 PDF 功能,包括提交表单数据。Edge 也是如此。事实上,所有现代 Web 浏览器都已停止支持 Adobe Reader 插件所依赖的 NPAPI 插件基础架构。如果您在浏览器中单击“order”按钮,_将不会发生任何事情_。
这就是为什么 Adobe 使使用最新版本的 Adobe Reader 提交表单数据成为可能的原因。早期版本的 Adobe Reader 不允许这样做,除非您的文档经过 Reader 扩展。 (如果您确切知道此更改何时进入 Adobe Reader,请留下评论。我尝试过谷歌搜索但未成功。)
要在使用 Web 打开 PDF 文档或表单时获得完整的 PDF 体验,您必须禁用浏览器的 PDF 查看器。以下是 Google Chrome 的步骤。
- 浏览到 chrome://plugins
- 单击 Chrome PDF Viewer 的禁用链接。
(为其他浏览器搜索类似的说明。)
如果您现在使用相同的链接在浏览器中打开表单,您的默认系统 PDF 查看器(确保它是 Adobe Reader)会在_浏览器外部_打开 PDF,如下所示。
从 Adobe Reader 提交表单数据
从 Adobe Reader 单击“order”按钮会将表单数据提交到端点 _http://www.tallcomponents.com/demos/pizza/order_。这是处理此请求的 ASP.NET MVC 控制器操作。
public class PizzaController : Controller
{
[HttpPost]
public ActionResult Order(Pizza pizza)
{
return View(pizza);
}
}
模型 Pizza
public class Pizza
{
public string Size { get; set; }
public string Tomatoes { get; set; }
public string Onions { get; set; }
public string Tuna { get; set; }
public string Cheese { get; set; }
public string Name { get; set; }
}
视图 Order.cshtml
@model Pizza <h2>Hi @Model.Name!</h2> <p> Thanks for ordering a @Model.Size pizza. Tomatoes: @Model.Tomatoes. Onions: @Model.Onions. Tuna: @Model.Tuna. Cheese: @Model.Cheese. </p>
注意 MVC 如何根据名称自动将表单数据映射到 Pizza 的成员。
单击“order”按钮后,将显示以下对话框。
单击“允许”后,Adobe Reader 会请求打开响应的权限。
显然,Adobe Reader 将响应保存到临时位置。单击“是”后,默认浏览器会显示响应。
这是符合预期的,但用户体验远非最佳。
返回 PDF 响应
上一个用例返回 HTML 作为响应。因此,浏览器实例会打开并显示 HTML。让我们看看如果我们返回 PDF 作为响应会发生什么。
我创建了订单披萨表单的_第二个_版本,您可以在此处打开: http://www.tallcomponents.com/demos/pizza/form2。
此 PDF 的“order”按钮将数据提交到_第二个_端点,该端点使用 PDFKit.NET 按如下方式返回 PDF 响应:
[HttpPost]
public ActionResult Order2(Pizza pizza)
{
Document document = new Document();
Page page = new Page(PageSize.Letter);
document.Pages.Add(page);
double margin = 72; // points
MultilineTextShape text = new MultilineTextShape(
margin, page.Height - margin, page.Width - 2 * margin);
page.Overlay.Add(text);
Fragment fragment = new Fragment(
string.Format("Hi {0}!, thanks for ordering a {1} pizza!",
pizza.Name, pizza.Size),
Font.Helvetica,
16);
text.Fragments.Add(fragment);
// send to browser
Response.ContentType = "application/pdf";
Response.AppendHeader("Content-disposition", "attachment; filename=file.pdf");
document.Write(Response.OutputStream);
return null;
}
如果我现在单击“order”按钮,将打开一个新的实例显示以下响应。
返回已展平的 PDF 表单
当 PDF 表单中的所有字段都已被替换为对应于表单数据的不可编辑图形时,该 PDF 表单被称为_已展平_ (flattened)。请注意,字段不仅仅是被禁用或设为只读,而是已被完全移除,并被非交互式内容所取代。让我们看看如何返回展平的表单作为响应。
我创建了订单披萨表单的_第三个_版本,您可以在此处打开: http://www.tallcomponents.com/demos/pizza/form3。
此 PDF 的“order”按钮将数据提交到_第三个_端点,该端点使用 PDFKit.NET 将提交的数据与原始表单合并并按如下方式展平表单:
[HttpPost]
public ActionResult Order3(Pizza pizza)
{
using (FileStream file = new FileStream(
Server.MapPath("~/Content/order-pizza3.pdf"),
FileMode.Open, FileAccess.Read))
{
// import submitted data into original form
Document document = new Document(file);
FormData data = FormData.Create(System.Web.HttpContext.Current.Request);
document.Import(data);
// flatten form
foreach (Field field in document.Fields)
{
foreach (Widget widget in field.Widgets)
{
widget.Persistency = WidgetPersistency.Flatten;
}
}
// send to browser
Response.ContentType = "application/pdf";
Response.AppendHeader("Content-disposition", "inline; filename=file.pdf");
document.Write(Response.OutputStream);
return null;
}
}
如果我现在单击“order”按钮,将打开一个新的 Adobe Reader 实例显示以下响应。
如果您尝试单击字段并更改值,您会发现什么都没有发生。如果您保存响应并使用 Adobe Acrobat 打开 PDF,您会发现没有字段。