Telerik blogs
How ToT2 Dark_1200x303

Explore what’s possible with the nested mail merge function with this home library demo.

Nested mail merge allows you to handle complex mail merge scenarios. It is useful when your data source contains properties that are business objects or collections of such objects, and you want to access their properties when performing the mail merge. This comes in handy when creating tables as well.

In this blog post, we will create a complete example of how to use this feature. We will begin by creating a sample business object, create a document in the code and finally perform the mail merge and save the resulting document.

The example creates a representation of a home library which catalogs books with their author, their description and their position.

Creating Our Library

We can start by creating the business object that will represent the home library. We will have two objects—an object that will hold the book details and an object that will hold the entire library. The books are separated by genre.

class HomeLibrary
{
public List<BookDetails> Fiction { get; }
public List<BookDetails> NonFiction { get; }
public List<BookDetails> Kids { get; }
public HomeLibrary()
{
this.Fiction = new List<BookDetails>();
this.NonFiction = new List<BookDetails>();
this.Kids = new List<BookDetails>();
}
}
class BookDetails
{
public BookDetails(string title, string description, string position, string author)
{
Title = title;
Description = description;
Position = position;
this.Author = author;
}
public string Title { get; set; }
public string Description { get; set; }
public string Position { get; set; }
public string Author { get; set; }
}

Now we can create a sample library that contains some books. In this example, the data is stored in CSV files and the properties are separated by the “#” character. The GetLibraryFromFile method reads the CSV files line by line and creates the book detail objects.

public static HomeLibrary CreateSampleLibrary()
{
HomeLibrary library = new HomeLibrary();
var nonFiction = GetLibraryFromFile(@"..\..\..\NonFiction.csv");
library.NonFiction.AddRange(nonFiction);
var fiction = GetLibraryFromFile(@"..\..\..\Fiction.csv");
library.Fiction.AddRange(fiction);
var kids = GetLibraryFromFile(@"..\..\..\Kids.csv");
library.Kids.AddRange(kids);
return library;
}
public static List<BookDetails> GetLibraryFromFile(string path)
{
List<BookDetails> library = new List<BookDetails>();
string separtor = "#";
StreamReader readFile = new StreamReader(path);
string line;
string[] row;
while ((line = readFile.ReadLine()) != null)
{
if (string.IsNullOrEmpty(line))
{
continue;
}
row = line.Split(separtor, StringSplitOptions.RemoveEmptyEntries);
BookDetails details = new BookDetails(row[0], row[2], row[3], row[1]);
library.Add(details);
}
readFile.Close();
return library;
}

Once we have the sample library that is populated with data, we can start creating the document. For each of the genres in our library, we will create a table. Each table will have rows for each book inside the genre and columns with the specific book details.

The AddTable method creates a table and adds the columns, then adds the nested mail merge fields as well. The first and the last merge fields are the key to this functionality. You need to start with the TableStart field followed by the name of the property that you want to access. Then you can access the underlying properties like in a regular merge field.

When done with the insertion of the fields related to the properties, you need to add a merge field with the TableEnd keyword followed by the same property name as well. More information about this is available in our documentation: Mail Merge.

static void Main(string[] args)
{
RadFlowDocument document = new();
document.StyleRepository.AddBuiltInStyle(BuiltInStyleNames.GetHeadingStyleIdByIndex(1));
document.StyleRepository.AddBuiltInStyle(BuiltInStyleNames.GetHeadingStyleIdByIndex(2));
RadFlowDocumentEditor editor = new(document);
Run heading = editor.InsertText("Listing the articles in my home library");
heading.Paragraph.StyleId = "Heading1";
AddTable(document, editor, "NonFiction");
AddTable(document, editor, "Fiction");
AddTable(document, editor, "Kids");
var collection = new List<HomeLibrary> { CreateSampleLibrary() };
var mergedDocument = document.MailMerge(collection);
DocxFormatProvider provider = new DocxFormatProvider();
File.WriteAllBytes(@"..\..\..\my_library.docx", provider.Export(mergedDocument));
}
private static void AddTable(RadFlowDocument document, RadFlowDocumentEditor editor, string
libraryName)
{
editor.InsertParagraph();
var run = editor.InsertText(libraryName);
run.Paragraph.StyleId = "Heading2";
var table = editor.InsertTable(2, 4);
table.PreferredWidth = new TableWidthUnit(TableWidthUnitType.Percent, 100);
document.StyleRepository.AddBuiltInStyle(BuiltInStyleNames.TableGridStyleId);
table.StyleId = BuiltInStyleNames.TableGridStyleId;
AddTextToCelll(table.Rows[0].Cells[0], "Title", true);
AddTextToCelll(table.Rows[0].Cells[1], "Author", true);
AddTextToCelll(table.Rows[0].Cells[2], "Description", true);
AddTextToCelll(table.Rows[0].Cells[3], "Position", true);
AddFieldToCelll(table.Rows[1].Cells[0], editor, "MERGEFIELD TableStart:" + libraryName);
AddFieldToCelll(table.Rows[1].Cells[0], editor, "MERGEFIELD Title");
AddFieldToCelll(table.Rows[1].Cells[1], editor, "MERGEFIELD Author");
AddFieldToCelll(table.Rows[1].Cells[2], editor, "MERGEFIELD Description");
AddFieldToCelll(table.Rows[1].Cells[3], editor, "MERGEFIELD Position");
AddFieldToCelll(table.Rows[1].Cells[3], editor, "MERGEFIELD TableEnd:" + libraryName);
editor.MoveToTableEnd(table);
editor.InsertParagraph();
}
private static void AddFieldToCelll(TableCell tableCell, RadFlowDocumentEditor editor, string
fieldCode)
{
Paragraph paragraph;
if (tableCell.Blocks.Count == 0)
{
paragraph = tableCell.Blocks.AddParagraph();
editor.MoveToParagraphStart(paragraph);
}
else
{
paragraph = tableCell.Blocks.Last() as Paragraph;
editor.MoveToParagraphEnd(paragraph);
}
editor.InsertField(fieldCode, "");
}
public static void AddTextToCelll(TableCell cell, string text, bool isbold)
{
var run = cell.Blocks.AddParagraph().Inlines.AddRun(text);
if (isbold)
{
run.FontWeight = FontWeights.Bold;
}
}

We are ready to test our project and examine the results. The merged document will have three tables—one for each genre in our library.

nested_mail_merge_blog_001 - three tables with genres nonfiction, fiction and kids.

Try It and Share Your Feedback

No matter if you are already familiar with Telerik Document Processing or will meet the libraries for the first time, hurry up and get the latest bits so you can take advantage of the different document management possibilities they provide:

Download a Free Trial

And I am sure I have told you many times that your input is valuable. We do listen. So, do not be shy—drop us a line to share your feedback in the comments section below or directly in our Document Processing Libraries Feedback Portal.


About the Author

Dimitar Karamfilov

Dimitar Karamfilov is a Support Officer in the UI for WinForms team. He joined Telerik after graduating from the Telerik Academy in 2013. Apart from work he likes outdoor activities and reading philosophy literature.

Related Posts

Comments

Comments are disabled in preview mode.