Mail Merge and Reporting

Text Control Reporting combines the power of a reporting tool and powerful word processing functionality - fully programmable and embeddable in your ASP.NET application.

Fundamental Concepts

Text Control Reporting is a reporting framework that can be used with all .NET based TX Text Control classes. Text Control Reporting is included in all TX Text Control .NET versions: Windows Forms, WPF and ASP.NET. Universal fundamental concepts, report types and supported merge field types are described in the Reporting User's Guide.

Getting Started

This getting started tutorial shows how to use the Template Designer TX Text Control Words with a sample template and database and illustrates how to integrate the merging process into your ASP.NET Web application.

The source code for this simple example is located in the following directories:

Language Project Location
C# %USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\CSharp\Documentation Tutorials\MailMerge\
VB.NET %USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\VB.NET\Documentation Tutorials\MailMerge\

Used TX Text Control classes:

Create Your Application

1. Open Visual Studio and create a new ASP.NET Empty Web Application application.

Image

2. In the Solution Explorer, select the project and choose Add New Item... from the Project main menu.

In the opened dialog Add New Item, select Web Form and confirm with Add.

Image

3. In the Solution Explorer, select the newly created Web Form and choose Component Designer from the View main menu.

Find the MailMerge component in the Toolbox and drag and drop an instance onto the Component Designer form.

Repeat this step with the ServerTextControl component.

Image

4. Select the MailMerge component and set the TextComponent property to serverTextControl1 - the name of the inserted instance of ServerTextControl.

Image

5. While the project is selected in the Solution Explorer, choose Add Existing Item... from the Project main menu.

In the opened dialog Add Existing Item, browse to the following sample folder:

%USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\CSharp\Documentation Tutorials\MailMerge\Tutorial\

or

%USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\VB.NET\Documentation Tutorials\MailMerge\Tutorial\

Select the following 2 files: template.docx and sample_db.xml and click the Add button.

Image

6. Select the Web Form in the Solution Explorer and choose Designer from the View main menu.

Find the Button control in the Toolbox and drag and drop an instance onto the Web Form.

Image

7. Double-click the button and insert the following code to the event handler:

protected void Button1_Click(object sender, EventArgs e)
{
    // create a DataSet from the sample XML data source
    System.Data.DataSet ds = new System.Data.DataSet();
    ds.ReadXml(Server.MapPath("sample_db.xml"), System.Data.XmlReadMode.Auto);

    // load the template
    mailMerge1.LoadTemplate(Server.MapPath("template.docx"),
        TXTextControl.DocumentServer.FileFormat.WordprocessingML);

    // merge the template with data
    mailMerge1.Merge(ds.Tables[0]);

    // save the document as PDF into a byte array
    byte[] data;
    mailMerge1.SaveDocumentToMemory(out data, TXTextControl.BinaryStreamType.AdobePDF, null);

    // return the document to the browser for download
    Response.Clear();
    Response.AddHeader("content-disposition",
        String.Format("attachment;filename={0}", "created_by_txtextcontrol.pdf"));
    Response.ContentType = "application/pdf";
    Response.BinaryWrite(data);
    Response.End();
}
Protected Sub Button1_Click(sender As Object, e As EventArgs)
    ' create a DataSet from the sample XML data source
    Dim ds As New System.Data.DataSet()
    ds.ReadXml(Server.MapPath("sample_db.xml"), System.Data.XmlReadMode.Auto)

    ' load the template
    mailMerge1.LoadTemplate(Server.MapPath("template.docx"), TXTextControl.DocumentServer.FileFormat.WordprocessingML)

    ' merge the template with data
    mailMerge1.Merge(ds.Tables(0))

    ' save the document as PDF into a byte array
    Dim data As Byte()
    mailMerge1.SaveDocumentToMemory(data, TXTextControl.BinaryStreamType.AdobePDF, Nothing)

    ' return the document to the browser for download
    Response.Clear()
    Response.AddHeader("content-disposition", [String].Format("attachment;filename={0}", "created_by_txtextcontrol.pdf"))
    Response.ContentType = "application/pdf"
    Response.BinaryWrite(data)
    Response.[End]()
End Sub

8. In the same code view, add the following code to the Page_Load event:

protected void Page_Load(object sender, EventArgs e)
{
    InitializeComponent();
}
Protected Sub Page_Load(sender As Object, e As EventArgs)
    InitializeComponent()
End Sub

Compile and start the application. Press the button to create the report that is returned as a PDF file.

Example - Creating a Simple Mail-Merge Letter Report

The class MailMerge encapsulates typical reporting tasks and can be connected to an instance of ServerTextControl. A template is simply loaded and data is passed from a DataSet, DataTable or an IEnumerable object. All merge fields and merge blocks are populated automatically using existing data relations.

This example is part the Sample Explorer project which includes all samples in one. The sources for this sample project can be found here:

Language Project Location
C# %USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\CSharp\Sample Explorer\
VB.NET %USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\VB.NET\Sample Explorer\

You will find the source code and the ASPX page of this sample in the Solution structure tree under: Reporting\create.aspx

Image

Example Description

In this demo, a MailMerge instance is connected to a ServerTextControl to create a simple report. A Mail-Merge report is a fully featured word processing document with merge fields, image placeholders or barcodes that is merged with data from one or many data sources.

For this demo, the data source, that contains merge field data such as address details, is an IEnumerable object.

Image

This business object is created based on data that is completed using HTML form fields. Change the pre-populated field values and click Create Report to start the merge process.

Image

A template is loaded into an instance of MailMerge. The created business object is used as the data source in the MergeObjects method. The created document is finally loaded into the UI component DocumentViewer.

protected void btnCreateReport_Click(object sender, EventArgs e)
{
    // build a data object for the merge process
    Reports.Add(CreateBusinessObject());

    // load the template
    mailMerge1.LoadTemplate(Server.MapPath("/documents/template.docx"),
        TXTextControl.DocumentServer.FileFormat.WordprocessingML);

    // merge the template with the IEnumerable business object
    mailMerge1.MergeObjects(Reports, true);

    // save the document to memory for preview purposes
    // in the DocumentViewer
    mailMerge1.SaveDocumentToMemory(out data,
        TXTextControl.BinaryStreamType.InternalUnicodeFormat, null);
    DocumentViewer1.LoadDocumentFromMemory(data,
        TXTextControl.DocumentServer.FileFormat.InternalUnicodeFormat);

    btn_DownloadReport.Visible = true;
}
Protected Sub btnCreateReport_Click(sender As Object, e As EventArgs)
    ' build a data object for the merge process
    Reports.Add(CreateBusinessObject())

    ' load the template
    mailMerge1.LoadTemplate(Server.MapPath("/documents/template.docx"), _
        TXTextControl.DocumentServer.FileFormat.WordprocessingML)

    ' merge the template with the IEnumerable business object
    mailMerge1.MergeObjects(Reports, True)

    ' save the document to memory for preview purposes
    ' in the DocumentViewer
    mailMerge1.SaveDocumentToMemory(data, _
        TXTextControl.BinaryStreamType.InternalUnicodeFormat, Nothing)
    DocumentViewer1.LoadDocumentFromMemory(data, _
        TXTextControl.DocumentServer.FileFormat.InternalUnicodeFormat)

    btn_DownloadReport.Visible = True
End Sub

Example - Creating a Table Report

Table reports display 1-dimensional, not hierarchical data in tabular form. Typically, the data that is used to merge a table report is stored in a single DataTable. Typically, this concept is used to present product lists, check lists or inventory items in a flat table form.

This example is part the Sample Explorer project which includes all samples in one. The sources for this sample project can be found here:

Language Project Location
C# %USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\CSharp\Sample Explorer\
VB.NET %USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\VB.NET\Sample Explorer\

You will find the source code and the ASPX page of this sample in the Solution structure tree under: Reporting\table.aspx

Image

Example Description

The data source in this sample is the same sample database that is used in the tutorial. The used template consists of a table that is part of a merge block. This block is repeated based on the number of data rows. The table itself contains two data rows with the same merge fields and a NEXT field at the end. With this setup, alternating table row colors are enabled.

Image

Uncheck some of the listed company names in the GridView control and click Create table report to start the merge process. Only the selected company names are added to the resulting table report which is returned as a PDF file.

Image

Example - Creating a Master-Detail Report

A Master-Detail report renders data from a hierarchical data source such as related tables or nested objects.

This example is part the Sample Explorer project which includes all samples in one. The sources for this sample project can be found here:

Language Project Location
C# %USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\CSharp\Sample Explorer\
VB.NET %USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\VB.NET\Sample Explorer\

You will find the source code and the ASPX page of this sample in the Solution structure tree under: Reporting\masterdetail.aspx

Image

Example Description

Nested related tables are recognized automatically and merged based on the merge block names in the template. The following report lists all sales and the sold products for each sale. Each sale is rendered on a new page in the final report.

Click on the button Create report to start the merge process.

Image

The sample database from the tutorial is used in this sample as well. The template consists of a table with 3 rows. The last row is a merge block (indicated through the red overlay background color) that should list all products.

Image

The complete template is repeated based on the number rows of the master table. The master table is passed in the Merge method and TX Text Control is matching the related tables automatically.

The resulting document is loaded into a DocumentViewer instance on the ASPX page.

protected void btn_CreateReport_Click(object sender, EventArgs e)
{
    // open the sample XML file and create DataSet
    DataSet ds = new DataSet();
    ds.ReadXml(Server.MapPath("/datasource/sample_db.xml"), XmlReadMode.Auto);

    // load the template
    mailMerge1.LoadTemplate(Server.MapPath("/documents/template_master.docx"),
        TXTextControl.DocumentServer.FileFormat.WordprocessingML);

    // pass the master table to start the merge process
    mailMerge1.Merge(ds.Tables["Sales_SalesOrderHeader"]);

    byte[] data;

    // save the document to memory as PDF
    mailMerge1.SaveDocumentToMemory(out data,
        TXTextControl.BinaryStreamType.InternalUnicodeFormat, null);

    DocumentViewer2.LoadDocumentFromMemory(data,
        TXTextControl.DocumentServer.FileFormat.InternalUnicodeFormat);

    // store document for a later download
    Session["data"] = data;
    btnDownloadPDF.Visible = true;
}
Protected Sub btn_CreateReport_Click(sender As Object, e As EventArgs)
    ' open the sample XML file and create DataSet
    Dim ds As New DataSet()
    ds.ReadXml(Server.MapPath("/datasource/sample_db.xml"), XmlReadMode.Auto)

    ' load the template
    mailMerge1.LoadTemplate(Server.MapPath("/documents/template_master.docx"), _
        TXTextControl.DocumentServer.FileFormat.WordprocessingML)

    ' pass the master table to start the merge process
    mailMerge1.Merge(ds.Tables("Sales_SalesOrderHeader"))

    Dim data As Byte()

    ' save the document to memory as PDF
    mailMerge1.SaveDocumentToMemory(data, _
        TXTextControl.BinaryStreamType.InternalUnicodeFormat, Nothing)

    DocumentViewer2.LoadDocumentFromMemory(data, _
        TXTextControl.DocumentServer.FileFormat.InternalUnicodeFormat)

    ' store document for a later download
    Session("data") = data
    btnDownloadPDF.Visible = True
End Sub

Use Case - Expense Report

This demo shows a master-detail report use case with a business object as the data source. The template contains master data (the purpose of the expense report, dates), employee information and the expenses in form of a table (detail information).

This example is part the Sample Explorer project which includes all samples in one. The sources for this sample project can be found here:

Language Project Location
C# %USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\CSharp\Sample Explorer\
VB.NET %USERPROFILE%\Documents\TX Text Control 28.0.NET Server for ASP.NET\Samples\ASP.NET\VB.NET\Sample Explorer\

You will find the source code and the ASPX page of this sample in the Solution structure tree under: Reporting\expenses.aspx

Image

Example Description

Simply Select a report from the GridView and click on Create report to start the merge process.

Image

An expense report contains general data such as the purpose of the expense, the pay period and a statement number. Additionally, it should list the employee details and finally, the expenses itself. The class diagram of the business object is illustrated below:

Image

  • Expenses: This is an IEnumerable wrapper object for the separate Expense reports. This object is passed to Text Control Reporting.
  • Expense: The expense report itself.
  • Employee: Information about the employee referenced in Expense.
  • LineItem: Details of each expense referenced in Expense.

The business object Expenses has some useful features. It contains XML Serialization attributes which allows us to use an XML data source that is deserialized into the business object automatically.

[XmlType("expenses", IncludeInSchema = true)]
public class Expenses : List<Expense>
{
    [XmlElement("expense")]
    public List<Expense> expenses { get; set; }
}

//...
<XmlType("expenses", IncludeInSchema := True)> _
Public Class Expenses
    Inherits List(Of Expense)
    <XmlElement("expense")> _
    Public Property expenses() As List(Of Expense)
        Get
            Return m_expenses
        End Get
        Set
            m_expenses = Value
        End Set
    End Property
    Private m_expenses As List(Of Expense)
End Class
'...

The attributes define which XML element is mapped to which member of the object. An XmlSerializer is used to deserialize the XML in order to create a new instance of the business object.

XmlSerializer serializer = new XmlSerializer(typeof(Expenses));
Expenses expenses = (Expenses)serializer.Deserialize(
    new StreamReader("expense_report_data.xml"));
Dim serializer As New XmlSerializer(GetType(Expenses))
Dim expenses As Expenses = DirectCast(serializer.Deserialize( _
    New StreamReader("expense_report_data.xml")), Expenses)

Additionally, we don't need to store all values in our XML file (or database in real life applications). The business object contains the logic and the code to calculate specific field values internally. All inverted members in the above illustration are not imported from the XML, but calculated on the fly in the business object itself.

The following code shows how the Total value is calculated based on existing values:

public double Total
{
    get
    {
        double dTotal = 0;

        foreach (LineItem item in this.LineItems)
        {
            dTotal += item.line_total;
        }

        return dTotal - this.Advances;
    }
}
Public ReadOnly Property Total() As Double
    Get
        Dim dTotal As Double = 0

        For Each item As LineItem In Me.LineItems
            dTotal += item.line_total
        Next

        Return dTotal - Me.Advances
    End Get
End Property

In order to start the merge process itself, only 1 line of code is required:

mailMerge1.MergeObjects(expenses);
mailMerge1.MergeObjects(expenses)

Text Control's Reporting engine MailMerge is mapping the merge fields to the business object members and related objects automatically.