TX Text Control .NET Server

Overview

Topic

What happens between loading a template and saving the merged document? Knowing how TX Text Control reporting works, from its layout model and the shape of the data to the events of a merge, makes the difference between a template that merges once and one that holds up with real data. These concepts apply to every template you merge, whatever its data source.

The MailMerge page in the Overview introduces the class, and Getting Started: Mail Merge walks through a first merge. The examples below use template.docx and the invoice data in data.json from the Simple Mail Merge tutorial.

The Reporting Model

TX Text Control reporting doesn't use a banded report designer with fixed header, body and footer bands. A template is an ordinary document, designed with the features of a word processor, such as tables, page columns, images, headers and footers:

  • The text flows. TX Text Control breaks lines and pages as the merged content grows. A table that doesn't fit on a page continues on the next one, with its header rows repeated, and paragraph settings such as "keep with next" or "page break before" control where pages break.
  • Templates contain no data connections. A template holds only the names of merge fields and merge blocks, never a connection string, so the same template works with different data.
  • Calculated values come with the data. Values such as totals can be calculated in your code or your business objects and merged like any other value.

Invoice template in TX Text Control Words with a font color palette open, merge fields for the customer and contact in shaped text frames, a Quick Facts table with order fields and a blue table header for the product lines

Pre-Shaped Data

MailMerge expects prepared data instead of querying a data source during the merge. You prepare the data in the hierarchy the document needs, as JSON or as business objects built from any number of sources, and merge it. This is called pre-shaped data. It keeps templates easy to understand, and merging is faster because the data already has the right structure. The one exception is a Windows Forms feature: with a Report Data Source Configuration file, MailMerge connects to a database and queries it itself, which Data Sources explains along with why .NET Server applications merge prepared data instead.

Internally, the hierarchy is converted into tables:

In the dataIn the template
Properties of the root objectMerge fields, such as InvoiceNumber
A nested objectMerge fields with dot notation, such as Customer.CustomerName
An arrayA merge block with the name of the array, such as LineItems, repeated for each item

The following diagram shows the tables of an order document: the root object provides company_name, the nested objects and arrays address, contacts and orders become tables of their own, and each order's articles array is a table related to its order:

Diagram of JSON data converted into related tables: a root table with company_name, address, contacts and orders, and articles with a nested product below orders

The records you pass to a merge method form the master table: the template is merged once for each record, so an array of 100 invoice objects creates 100 invoices. MailMerge merges all records it's given. To leave records or rows out, shape the data before merging, or store filter instructions in the template, as Sorting and Filtering describes. Data Sources lists the kinds of data you can merge.

What a Merge Processes

A merge fills more than merge fields. These elements of a template receive data:

ElementWhat happensDescribed in
MERGEFIELD fieldsThe field is replaced with the value of the same nameMerge Fields
DATE fieldsThe current date is inserted in the field's formatMerge Fields
IF fieldsOne of two texts is inserted, depending on a comparisonConditional Content
INCLUDETEXT fieldsAnother document, a sub-template, is included in place of the fieldTemplate Design
Merge blocksThe block is repeated for each record of a collection, also in nested levelsMerge Blocks
Named images and image: fieldsThe image is replaced with image dataImages
Named barcodesThe barcode encodes the valueBarcodes
ChartsThe chart is filled through the data relation stored in the template; only outside merge blocksCharts and Barcodes
Form fieldsDepending on FormFieldMergeType, not merged, preselected or replaced with the valueMerge Fields

Merge fields in headers and footers are merged as well.

Create One Document or Separate Documents

MergeJsonData, MergeObjects and Merge have an optional second parameter, append. With true, the default, all records are merged into one document, and each record starts a new section. With false, a separate document is created for each record.

The ServerTextControl holds only the last document after a merge with append set to false. To get every document, handle the DataRowMerged event, which occurs after each record. Its MergedRow property contains the document of the record in the TX Text Control format. The following program creates one PDF file for each invoice in data.json:

using TXTextControl;
using TXTextControl.DocumentServer;

string jsonData = File.ReadAllText("data.json");
List<byte[]> documents = new List<byte[]>();

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    LoadSettings loadSettings = new LoadSettings()
    {
        ApplicationFieldFormat = ApplicationFieldFormat.MSWord,
        LoadSubTextParts = true
    };

    tx.Load("template.docx", StreamType.WordprocessingML, loadSettings);

    using (MailMerge mailMerge = new MailMerge { TextComponent = tx })
    {
        // Collect the document of each record
        mailMerge.DataRowMerged += (sender, e) => documents.Add(e.MergedRow);

        // false: a separate document for each record
        mailMerge.MergeJsonData(jsonData, false);
    }
}

for (int i = 0; i < documents.Count; i++)
{
    using (ServerTextControl document = new ServerTextControl())
    {
        document.Create();
        document.Load(documents[i], BinaryStreamType.InternalUnicodeFormat);
        document.Save($"invoice-{i + 1}.pdf", StreamType.AdobePDF);
    }
}

Console.WriteLine($"{documents.Count} invoices created");

To see several documents, add a second invoice object with the same structure to the array in data.json. When all records are merged into one document, a first page header shows the data of the first record in every section unless it's disconnected from the previous section, as the blog article Merging Multiple Records with First-Page Headers and Footers explains.

Get Separate Documents From DataSourceManager

DataSourceManager.Merge returns the documents of the records directly, without an event handler. It takes the template as a byte array in the TX Text Control format and returns a list with one document in the same format for each record. An overload limits the number of documents, for example to preview the first records of a large data source:

using TXTextControl;
using TXTextControl.DocumentServer.DataSources;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("template.tx", StreamType.InternalUnicodeFormat);

    byte[] template;
    tx.Save(out template, BinaryStreamType.InternalUnicodeFormat);

    DataSourceManager dataSourceManager = new DataSourceManager();
    dataSourceManager.LoadJson(File.ReadAllText("data.json"));

    MergeSettings mergeSettings = new MergeSettings()
    {
        AsSingleDocument = false
    };

    // One document for each of the first 10 records
    IList<byte[]> documents = dataSourceManager.Merge(template, 10, tx, mergeSettings);

    for (int i = 0; i < documents.Count; i++)
    {
        using (ServerTextControl document = new ServerTextControl())
        {
            document.Create();
            document.Load(documents[i], BinaryStreamType.InternalUnicodeFormat);
            document.Save($"invoice-{i + 1}.pdf", StreamType.AdobePDF);
        }
    }
}

The code works as follows:

  • LoadJson loads the data that Merge merges. The other Load methods of DataSourceManager load business objects, a DataSet or XML.
  • The second parameter of Merge is the maximum number of documents; the overload without it merges all records. The third parameter is a ServerTextControl, which Merge needs for licensing purposes.
  • MergeSettings sets whether the results are combined into one document, where each record starts on a new page, and whether empty fields, blocks, images and lines and trailing whitespace are removed. All removal settings are true by default, unlike RemoveEmptyLines and RemoveEmptyImages of MailMerge.

Put Several Records on One Page

Address labels, name badges and shipping labels print several records on one page. Such a template repeats the same fields in each label, usually in the cells of a table, and ends each label with a NEXT field. The NEXT field moves on to the next record, so the next label shows the following record instead of the same one. The following label of a shipping label template has merge fields, barcode placeholders and a NEXT field at its end:

Shipping label template with a company logo, a barcode, merge fields for the customer's name and address, a 2D barcode and a NEXT field at the bottom

In MS Word, NEXT is a field of its own; in code, the NextField adapter creates it, as Fields in Document Processing lists. The same field alternates the colors of table rows, as Styling describes. A NEXTIF field moves on only when a comparison is true, as Conditional Content shows.

Skip or Stop Records During the Merge

MailMerge merges every record it's given, so the best place to leave out records is the data. When the decision can only be made during the merge, handle DataRowMerged:

The following program leaves out the second record of data.json; DataRowNumber counts the records from 0:

using TXTextControl;
using TXTextControl.DocumentServer;

// Created once: an empty document replaces a skipped record
byte[] emptyDocument;

using (ServerTextControl empty = new ServerTextControl())
{
    empty.Create();
    empty.Save(out emptyDocument, BinaryStreamType.InternalUnicodeFormat);
}

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("template.tx", StreamType.InternalUnicodeFormat);

    using (MailMerge mailMerge = new MailMerge { TextComponent = tx })
    {
        mailMerge.DataRowMerged += (sender, e) =>
        {
            // Leave out the second record
            if (e.DataRowNumber == 1)
            {
                e.MergedRow = emptyDocument;
            }
        };

        mailMerge.MergeJsonData(File.ReadAllText("data.json"));
    }

    tx.Save("output.pdf", StreamType.AdobePDF);
}

To stop instead, set e.Cancel = true in the handler, for example after a maximum number of records, and put the merge call in a try block with a catch (MergeCanceledException).

Let MailMerge Load and Save the Document

Instead of loading the template into the ServerTextControl yourself, you can let MailMerge load it and save the result:

using TXTextControl;
using TXTextControl.DocumentServer;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    using (MailMerge mailMerge = new MailMerge { TextComponent = tx })
    {
        if (!mailMerge.LoadTemplate("template.tx", FileFormat.InternalUnicodeFormat))
        {
            Console.WriteLine("The template could not be loaded.");
            return;
        }

        mailMerge.MergeJsonData(File.ReadAllText("data.json"));
        mailMerge.SaveDocument("output.pdf", StreamType.AdobePDF, new SaveSettings());
    }
}

For DOCX templates with merge blocks, loading with tx.Load and LoadSettings, as in the previous example, lets you set LoadSubTextParts explicitly.

Follow the Merge With Events

Events of MailMerge let your code react while a merge runs:

EventOccursTypical use
FieldMergedFor each field, whether it received data or notChange the merged text, find fields without data
BlockMergingBefore a merge block is mergedLog the blocks and their data
BlockRowMergedAfter each row of a merge blockChange the content or formatting of a merged row
DataRowMergedAfter each record of the master tableGet or change the document of a record
ImageMerged, ImageFieldMergedAfter an image or an image field has been mergedChange the merged image
BarcodeMerged, ChartMerged, FormFieldMergedAfter a barcode, a chart or a form field has been mergedCheck the merged object
IncludeTextMergingWhen an INCLUDETEXT field is mergedSupply the included document from your code

The arguments of FieldMerged, FormFieldMerged, ImageMerged and ImageFieldMerged tell where in the template the merged element is: MergeBlockName is the name of its merge block, and NestingDepth the level of that block. FieldMerged, BlockRowMerged and DataRowMerged provide the merged content as a byte array in the TX Text Control format, which you can load into another ServerTextControl, change and assign back.

Merge in an ASP.NET Core Web API

A common service receives a template and JSON data over HTTP and returns the merged PDF. The merge code is the same as in a console application; REST Services builds such an endpoint, answers invalid templates and JSON data with a bad request and moves long-running merges into background jobs.

Next Steps