Attachments
Invoices often travel as a PDF with a machine-readable copy inside: the XML of a ZUGFeRD invoice, the JSON data of a report or the spreadsheet behind a contract. With TX Text Control .NET for WPF, such embedded files are part of the document in the TextControl. Code attaches them before the document is saved as PDF, the DocumentSettings panel of the WPF sidebar lets users manage them, and a loaded PDF brings its attachments along. The sections below cover the classes, the relationship and MIME type that PDF/A-3 asks for, electronic invoices and extracting attachments from received PDF files.
For a first PDF saved from the editor window, see Create a PDF in Getting Started.
How Attachments Work
Each attachment is an EmbeddedFile: a file name, the file's data and optional properties. Only PDF and PDF/A carry embedded files. They are written when the document is saved with StreamType.AdobePDF or StreamType.AdobePDFA, and every other format leaves them out.
| Property | Contains |
|---|---|
| FileName | The file name, optionally with a relative or absolute path; read only |
| Data | The content, as a string or a byte array; read only |
| MIMEType | The MIME type, for example application/json; application/octet-stream if none is set |
| Relationship | How the file relates to the document; PDF/A only |
| Description | A description of the file |
| CreationDate, LastModificationDate | The dates of the file; if null, the PDF document's dates are used |
The EmbeddedFile constructor takes the file name, the data as a byte array or a string, and XMP metadata or null. XMP metadata consists of rdf:Description elements, which are added to the metadata of the PDF, as electronic invoices need. Another overload takes a SafeFileHandle and reads the file only when the document is saved.
Attachments live in three properties:
- WPF.TextControl.DocumentSettings
.EmbeddedFiles holds the attachments of the document in the editor, ornull. Loading a PDF fills it, the sidebar panel works on it, and every save as PDF embeds its files. - SaveSettings.EmbeddedFiles sets the attachments of a single save, instead of those of
DocumentSettings. - LoadSettings.EmbeddedFiles reports the attachments of a loaded PDF, provided PDFImportSettings includes
LoadEmbeddedFiles, as it does by default.
Choose the Relationship and MIME Type
A PDF/A-3 document needs a MIME type and a relationship for each embedded file. The relationship is a string, with these predefined values:
| Relationship | Use it for |
|---|---|
Source | The document the PDF was made from, for example a DOCX file |
Data | Data for software, such as XML, JSON or CSV |
Alternative | Another form of the same content, such as the XML of a ZUGFeRD or Factur-X invoice |
Supplement | Additional material, such as appendices |
Unspecified | Anything else |
Typical MIME types are application/xml, application/json, text/csv, image/png and image/jpeg. The blog article Why Defining MIME Types for PDF/A Attachments Is Essential describes what validators check.
Attach a File When Saving
The following Click handler goes into MainWindow.xaml.cs, which needs using System;, using System.IO;, using System.Windows; and using TXTextControl;. It embeds data.json from the application folder in the document of textControl1 and saves a PDF/A file:
private void attachDataButton_Click(object sender, RoutedEventArgs e)
{
// PDF/A requires fonts that can be embedded
textControl1.FontSettings.EmbeddableFontsOnly = true;
EmbeddedFile data = new EmbeddedFile("data.json", File.ReadAllBytes("data.json"), null)
{
Description = "Invoice data",
MIMEType = "application/json",
Relationship = "Data",
CreationDate = DateTime.Now
};
SaveSettings saveSettings = new SaveSettings()
{
EmbeddedFiles = new EmbeddedFile[] { data }
};
textControl1.Save("invoice.pdf", StreamType.AdobePDFA, saveSettings);
}
The code works as follows:
- FontSettings.EmbeddableFontsOnly allows only fonts that can be embedded, a requirement of PDF/A. The rules of the format are in PDF/A.
EmbeddedFilesis an array, so a singleSaveembeds as many files as you pass.- The array in
SaveSettingsapplies to this file only. Assign it totextControl1.DocumentSettings.EmbeddedFilesinstead to make the attachment part of the document for every later save.
A PDF reader lists the attachments in a separate panel. In Adobe Acrobat, it shows each file with its description:

Let Users Manage Attachments in the Sidebar
Set the ContentLayout of a WPF Sidebar to DocumentSettings, and the sidebar shows the properties of the document. Its Embedded Files in PDF category lists DocumentSettings.EmbeddedFiles: users add a file with Add..., write one to disk with Save..., delete it with Remove and edit its description, relationship and MIME type with Edit.... For a file added this way, the panel picks the MIME type from the file name extension.
The following XAML docks such a sidebar at the left of the editor, inside the DockPanel of MainWindow.xaml and before the TextControl element. The WPF prefix is the namespace declaration Visual Studio adds with the first TX Text Control element:
<WPF:Sidebar x:Name="sidebarLeft" DockPanel.Dock="Left" HorizontalAlignment="Left"
ContentLayout="DocumentSettings" IsShown="False" TextControl="textControl1" />
With HorizontalAlignment set to Left, the panel uses its vertical layout. A button opens it:
private void documentSettingsButton_Click(object sender, RoutedEventArgs e)
{
sidebarLeft.ContentLayout = Sidebar.SidebarContentLayout.DocumentSettings;
sidebarLeft.IsShown = true;
}
Sidebar is in the namespace TXTextControl.WPF, so MainWindow.xaml.cs needs using TXTextControl.WPF; for this handler. The same panel edits the title, author, subject, keywords and custom properties, as Document Metadata shows. Its elements are named after the values of DocumentSettingsItem, such as TXITEM_EmbeddedFilesPanel, which FrameworkElement.FindName finds when you want to hide one. Sidebar and Panels covers the sidebar in general.
Create an Electronic Invoice
A ZUGFeRD or Factur-X invoice is a PDF/A-3 document that people read, with the invoice data attached as XML that accounting software processes. XRechnung XML can be embedded in the same way since ZUGFeRD 2.1. TX Text Control creates the readable part and embeds the XML, which comes from your ERP system or a library such as ZUGFeRD-csharp.
The following handler attaches factur-x.xml to the invoice in the editor, passes the XMP metadata of metadata.xml that declares the standard, and saves PDF/A-3:
private void createEInvoiceButton_Click(object sender, RoutedEventArgs e)
{
// PDF/A requires fonts that can be embedded
textControl1.FontSettings.EmbeddableFontsOnly = true;
// The XML invoice, created by your ERP system or an e-invoice library
byte[] invoiceXml = File.ReadAllBytes("factur-x.xml");
// rdf:Description elements that declare the invoice standard
string metadata = File.ReadAllText("metadata.xml");
EmbeddedFile invoice = new EmbeddedFile("factur-x.xml", invoiceXml, metadata)
{
Description = "factur-x",
MIMEType = "application/xml",
Relationship = "Alternative",
LastModificationDate = DateTime.Now
};
SaveSettings saveSettings = new SaveSettings()
{
EmbeddedFiles = new EmbeddedFile[] { invoice }
};
textControl1.Save("e-invoice.pdf", StreamType.AdobePDFA, saveSettings);
}
metadata.xml holds the XMP extension schema of ZUGFeRD: conformance level, document type, version and the name of the embedded file. Its full content, and XML created with ZUGFeRD-csharp, are in the blog article Creating ZUGFeRD 2.3 (XRechnung, Factur-X) Documents with .NET C#. Validate the result with a ZUGFeRD or Factur-X validation service, and consider signing the PDF so that changes to the invoice become detectable; see Digital Signatures.
Extract Attachments From a PDF
The static class Attachments of the TXTextControl.DocumentServer.PDF namespace reads the attachments of a PDF file without loading it into the editor. Its namespace contains a second class named EmbeddedFile, so importing it next to TXTextControl makes the name ambiguous. Use an alias instead: using Pdf = TXTextControl.DocumentServer.PDF;.
The following handler asks for a PDF file and a target folder with the dialogs of Microsoft.Win32 (using Microsoft.Win32;) and saves each attachment into the folder:
private void extractAttachmentsButton_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openDialog = new OpenFileDialog() { Filter = "PDF documents (*.pdf)|*.pdf" };
OpenFolderDialog folderDialog = new OpenFolderDialog();
if (openDialog.ShowDialog(this) != true || folderDialog.ShowDialog(this) != true)
{
return;
}
Pdf.EmbeddedFile[] files = Pdf.Attachments.GetFromFile(openDialog.FileName)
?? Array.Empty<Pdf.EmbeddedFile>();
foreach (Pdf.EmbeddedFile file in files)
{
// The file name can contain a path
string path = System.IO.Path.Combine(folderDialog.FolderName, System.IO.Path.GetFileName(file.FileName));
if (file.HasBinaryData)
{
File.WriteAllBytes(path, file.BinaryData);
}
else if (file.HasStringData)
{
File.WriteAllText(path, file.StringData);
}
}
MessageBox.Show(this, $"{files.Length} attachments saved to {folderDialog.FolderName}");
}
The code works as follows:
- The WPF dialogs return a
bool?, which istruewhen the user confirmed.OpenFolderDialogis part of WPF since .NET 8. - GetFromFile takes the full path of a PDF file, GetFromData a PDF in a byte array. Both return DocumentServer.PDF.EmbeddedFile objects with read-only properties; the MIME type is called MimeType here.
- HasBinaryData and HasStringData say whether BinaryData or StringData holds the content.
The XML of an e-invoice is the attachment with the relationship Alternative and a MimeType that ends with xml, because older invoices use text/xml and newer ones application/xml.
Keep, Add and Replace Attachments
A PDF loaded into textControl1 puts its attachments into DocumentSettings.EmbeddedFiles, and saving as PDF embeds them again. A user who opens, edits and saves a PDF therefore keeps its attachments. With SaveSettings.EmbeddedFiles, one save gets exactly the array you pass. To add a file to the existing ones, extend the array of DocumentSettings (the handler needs using System.Collections.Generic;):
private void addAttachmentButton_Click(object sender, RoutedEventArgs e)
{
// The attachments of the loaded PDF, or none
List<EmbeddedFile> files = new List<EmbeddedFile>(
textControl1.DocumentSettings.EmbeddedFiles ?? Array.Empty<EmbeddedFile>());
files.Add(new EmbeddedFile("line-items.csv", File.ReadAllBytes("line-items.csv"), null)
{
Description = "Line items",
MIMEType = "text/csv"
});
// Saved with the document from now on
textControl1.DocumentSettings.EmbeddedFiles = files.ToArray();
textControl1.Save("invoice-with-line-items.pdf", StreamType.AdobePDF);
}
Attachments are loaded only if LoadSettings.PDFImportSettings contains LoadEmbeddedFiles. When you set the value yourself, for example to PDFImportSettings.GenerateLines, add LoadEmbeddedFiles to it; the other import settings are described in Loading Documents. The saved file is a new PDF built from the editor's content, carrying the same attachments.
Next Steps
- PDF/A: save archivable PDF/A-3 documents.
- Document Metadata: set and read title, author and keywords.
- Signing PDF Documents: sign a PDF together with its attachments.
- Sidebar and Panels: the WPF sidebar and its DocumentSettings panel.