This is the second post in a series dedicated to the use of our Office File API library (v19.2 and higher) within server-side Blazor apps. If you have yet to read our previous post, feel free to do so using the following link:
Office File API – How to Convert Documents (DOCX, XLSX, PDF) within Your Blazor Server Apps.
In this post, we'll show you how to create a Blazor Server application that leverages the capabilities of our Word Processing Document API. This sample app will generate multiple business letters based on a template. Users can download these letters in PDF format or send them via e-mail.
Prerequisites
- Visual Studio 2019 with the ASP.NET and web development workload
- .NET Core 3.1 SDK
Source Code
You can download a complete sample project from the following GitHub repository:
Word Processing – How to Generate and Send Business Letters within Your Blazor Server Apps
Step 1: Create a Blazor Server App
Create a new project in Visual Studio and select the Blazor App template.
Specify project name and location. In the next window, select Blazor Server App and click Create.
Step 2: Install DevExpress NuGet Packages
Visit nuget.devexpress.com to obtain the DevExpress NuGet feed URL. Register your feed URL as a package source in the NuGet Package Manager and install the following packages.
DevExpress.Document.Processor
This package contains the DevExpress Office File API components. You need an active license for the DevExpress Office File API Subscription or the DevExpress Universal Subscription to use this package in production code.
DevExpress.Blazor
Contains DevExpress Blazor UI components. This product line is available as part of the DevExpress Universal, DXperience, or ASP.NET Subscription.
If you are new to NuGet Packages, please refer to the following installation guide: Install DevExpress Components Using NuGet Packages.
Step 3: Create a Data Model
Create new SampleData.cs file within the Data folder.
Define a Recipient class to store information about recipients.
public class Recipient { public Recipient(string companyName, string contactName, string contactTitle, string address, string city, string postalCode, string email) { CompanyName = companyName; ContactName = contactName; ContactTitle = contactTitle; Address = address; City = city; PostalCode = postalCode; Email = email; } public string CompanyName { get; set; } public string ContactName { get; set; } public string ContactTitle { get; set; } public string Address { get; set; } public string City { get; set; } public string PostalCode { get; set; } public string Email { get; set; } }
Create a Sender class to store information about senders.
public class Sender { public Sender(string firstName, string lastName, string title) { FirstName = firstName; LastName = lastName; Title = title; } public string FirstName { get; set; } public string LastName { get; set; } public string Title { get; set; } public string FullName => $"{FirstName} {LastName}"; }
Create lists of Recipient and Sender objects with sample data. We will use these lists to supply data for the UI components in our app.
class SampleData { public List<Recipient> RecipientList { get; } public List<Sender> SenderList { get; } public SampleData() { RecipientList = CreateRecipientList(); SenderList = CreateSenderList(); } List<Recipient> CreateRecipientList() { return new List<Recipient>() { new Recipient("Alfreds Futterkiste", "Maria Anders", "Sales Representative", "Obere Str. 57", "Berlin", "12209", "mariaanders@example.com"), new Recipient("Ana Trujillo Emparedados y helados", "Ana Trujillo", "Owner", "Avda. de la Constitucion, 2222", "Mexico D.F.", "5021", "anatrujillo@example.com"), new Recipient("Antonio Moreno Taqueria", "Antonio Moreno", "Owner", "Mataderos 2312", "Mexico D.F.", "5023", "antoniomoreno@example.com"), new Recipient("Around the Horn", "Thomas Hardy", "Sales Representative", "120 Hanover Sq.", "London", "WA1 1DP", "thomashardy@example.com"), new Recipient("Berglunds snabbkop", "Christina Berglund", "Order Administrator", "Berguvsvegen 8", "Luleе", "S-958 22", "christinaberglund@example.com"), new Recipient("Blauer See Delikatessen", "Hanna Moos", "Sales Representative", "Forsterstr. 57", "Mannheim", "68306", "hannamoos@example.com"), new Recipient("Blondel pure et fils", "Frederique Citeaux", "Marketing Manager", "24 Place Kleber", "Strasbourg", "67000", "frederiqueciteaux@example.com"), new Recipient("Bolido Comidas preparadas", "Martin Sommer", "Owner", "C-Araquil, 67", "Madrid", "28023", "martinsommer@example.com"), new Recipient("Bon app'", "Laurence Lebihan", "Owner", "12, rue des Bouchers", "Marseille", "13008", "laurencelebihan@example.com") }; } List<Sender> CreateSenderList() { return new List<Sender>() { new Sender("Nancy", "Davolio", "Sales Representative"), new Sender("Andrew", "Fuller", "Vice President, Sales"), new Sender("Janet", "Leverling", "Sales Representative"), new Sender("Margaret", "Peacock", "Sales Representative"), new Sender("Steven", "Buchanan", "Sales Manager") }; } }
Step 4: Add DevExpress Blazor UI Components and Bind Them to Data
Add the following line to the HEAD section of the Pages/_Host.cshtml file to register DevExpress resources:
<head> <!--...--> <link href="_content/DevExpress.Blazor/dx-blazor.css" rel="stylesheet" /> </head>
Open the _Imports.razor file and add the following namespace:
@using DevExpress.Blazor
Apply the DevExpress Blazing Berry theme to the app as described in this help topic: Apply a DevExpress Bootstrap Theme.
Design the application UI. Our app includes the following DevExpress Blazor UI components:
- DxFormLayout - Form Layout component that allows you to construct responsive and auto-aligned edit forms.
- DxComboBox - A combo box. The Data property specifies the data source for the editor (SenderList in our example). The TextFieldName property allows you to select a data source field whose values must appear in the editor's drop-down list (we use the Sender.FullName values). The Value property specifies the selected item.
- DxButton - A button. Use the Text property to define the button's text.
- DxDataGrid - A Data Grid component. The Data property allows you to bind the grid to a data source (RecipientList here). Create three columns (DxDataGridColumn type). Use the DxDataGridColumn.Field property to bind the first two columns to data source fields (Recipient.ContactName and Recipient.Email). The last column will contain buttons to generate and download letters in PDF format.
Open the Index.razor file and change its code as follows:
@page "/" <div class="container"> <div class="card mt-3"> <div class="card-header"> Word (RTF) Mail Merge </div> <div class="card-body px-0"> <DxFormLayout> <DxFormLayoutItem ColSpanMd="12"> <Template> <p> This example uses the Word Processing Document API to generate personalized letters based on a template. Select a sender and click <b>"Send emails"</b> to send letters to all recipients. Click <b>"Download"</b> to download a letter in PDF format for a specific recipient. </p> </Template> </DxFormLayoutItem> <DxFormLayoutItem Caption="From:" ColSpanMd="4"> <Template> <DxComboBox Data="@dataSource.SenderList" TextFieldName="FullName" @bind-Value="@SelectedSender" CssClass="p-0" /> </Template> </DxFormLayoutItem> <DxFormLayoutItem> <Template> <DxButton Text="Send emails" /> </Template> </DxFormLayoutItem> </DxFormLayout> <div class="col"> <DxDataGrid Data="@dataSource.RecipientList" SelectionMode="DataGridSelectionMode.None" CssClass="mt-3" ShowPager="false"> <DxDataGridColumn Field="@nameof(Recipient.ContactName)" Caption="Recipient" /> <DxDataGridColumn Field="@nameof(Recipient.Email)" Caption="Email" /> <DxDataGridColumn Caption="Attachment" Width="150px"> <DisplayTemplate> @{ <DxButton Text="Download" CssClass="btn-block" /> } </DisplayTemplate> </DxDataGridColumn> </DxDataGrid> </div> </div> </div> </div>
@code { SampleData dataSource; Sender selectedSender; Sender SelectedSender { get => selectedSender; set { selectedSender = value; InvokeAsync(StateHasChanged); } } protected override Task OnInitializedAsync() { dataSource = new SampleData(); selectedSender = dataSource.SenderList[0]; return base.OnInitializedAsync(); } }
Step 5: Use Mail Merge to Generate Personalized Letters
Our Word Processing Document API fully supports mail merge operations. The API allows you to create multiple documents based on a single template. Execute the following actions to initiate mail merge within your app:
Specify a data source for Mail Merge. Open the SampleData.cs file and create a DataTable instance:
DataTable dataTable; public SampleData() { // ... dataTable = new DataTable(); CreateDataTableColumns(dataTable); } void CreateDataTableColumns(DataTable dataTable) { dataTable.Columns.Add("FirstName", typeof(string)); dataTable.Columns.Add("LastName", typeof(string)); dataTable.Columns.Add("Title", typeof(string)); dataTable.Columns.Add("CompanyName", typeof(string)); dataTable.Columns.Add("ContactName", typeof(string)); dataTable.Columns.Add("ContactTitle", typeof(string)); dataTable.Columns.Add("Address", typeof(string)); dataTable.Columns.Add("City", typeof(string)); dataTable.Columns.Add("PostalCode", typeof(string)); dataTable.Columns.Add("Email", typeof(string)); }
Implement the following method to populate the data table with values based on the selected sender and recipient.
public DataTable GetDataSource(Sender sender, Recipient recipient) { dataTable.Rows.Clear(); dataTable.Rows.Add(sender.FirstName, sender.LastName, sender.Title, recipient.CompanyName, recipient.ContactName, recipient.ContactTitle, recipient.Address, recipient.City, recipient.PostalCode, recipient.Email); return dataTable; }
Create a mail merge template. The template should contain merge fields that will be replaced with data source values during Mail Merge.
Feel free to download the MailMerge.rtf template to proceed. Add this file to the Data folder of the project.
Open the Index.razor file and add the following namespaces:
@using System.IO @using DevExpress.XtraRichEdit
Load the mail merge template using a RichEditDocumentServer instance.
@code { RichEditDocumentServer documentServer; // ... protected override Task OnInitializedAsync() { documentServer = new RichEditDocumentServer(); documentServer.LoadDocument("Data/MailMerge.rtf", DocumentFormat.Rtf); // ... } }
Implement the following method to run Mail Merge. The output document is exported to PDF.
MemoryStream CreateAttachment(Recipient recipient) { using (RichEditDocumentServer resultDocumentServer = new RichEditDocumentServer()) { MemoryStream stream = new MemoryStream(); // Fill the data table with information about the selected sender // and recipient. Use the table as a data source for mail merge. documentServer.Options.MailMerge.DataSource = dataSource.GetDataSource(selectedSender, recipient); documentServer.Options.MailMerge.ViewMergedData = true; // Execute mail merge. documentServer.MailMerge(resultDocumentServer.Document); // Export the result to PDF. resultDocumentServer.ExportToPdf(stream); stream.Seek(0, SeekOrigin.Begin); return stream; } }
Step 6: Download Letters in PDF Format to the Browser
In our example, we will use JavaScript Interop to download generated letters in PDF format on the client side.
Create a wwwroot/js/exportToPdf.js file with the following JavaScript function:
function exportToPdf(filename, base64Content) { var link = document.createElement('a'); link.download = filename; link.href = "data:application/pdf;base64," + base64Content; document.body.appendChild(link); link.click(); document.body.removeChild(link); }
Embed the script in the Pages/_Host.cshtml page before the closing
</body>
tag.<script src="js/exportToPdf.js"></script>
Inject an instance of the IJSRuntime object into the Index.razor page.
@page "/" @inject IJSRuntime JS
Handle the Click event of the Download button in the Attachment column of the grid. When a user clicks Download, the Mail Merge process is executed for the selected sender and recipient. The generated letter is exported to PDF and saved on the client.
<DxDataGridColumn Caption="Attachment" Width="150px"> <DisplayTemplate> @{ Recipient recipient = context as Recipient; <DxButton Text="Download" CssClass="btn-block" Click="@((MouseEventArgs args) => DownloadPdf(recipient))"/> } </DisplayTemplate> </DxDataGridColumn>
void DownloadPdf(Recipient recipient) { // Execute mail merge. using (MemoryStream stream = CreateAttachment(recipient)) { // Download letter in PDF format on the client. JS.InvokeVoidAsync("exportToPdf", "output.pdf", Convert.ToBase64String(stream.ToArray())); } }
Step 7: Send Letters via E-mail
You can upgrade your application to allow users to send generated letters via e-mail. This example uses the MailKit open source library to send e-mail messages.
- Install the MailKit NuGet package.
Add the following namespaces to the Index.razor file:
@using MimeKit; @using MailKit.Net.Smtp
Handle the Click event of the Send emails button.
<DxButton Click="SendEmails" Text="Send emails" />
Add the following code to send e-mail messages to all recipients when a user clicks the button. Letters will be attached to messages as PDF files.
@code { void SendEmails(MouseEventArgs mouseEventArgs) { // Obtain a list of recipients. List<Recipient> recipientList = dataSource.RecipientList; for (int i = 0; i < recipientList.Count; i++) { // Execute mail merge to generate a letter for each recipient. using (MemoryStream attachmentStream = CreateAttachment(recipientList[i])) { // Create e-mail message for each recipient. // Attach letter to the message as a PDF file. MimeMessage message = CreateMessage("YOUR_EMAIL_ADDRESS", recipientList[i], attachmentStream); // Send the message. SendMessage(message); } } } MimeMessage CreateMessage(string from, Recipient recipient, MemoryStream attachmentStream = null) { var message = new MimeMessage(); // Specify the sender's address. message.From.Add(new MailboxAddress(SelectedSender.FullName, from)); // Specify the recipient's address. message.To.Add(new MailboxAddress(recipient.ContactName, recipient.Email)); message.Subject = "Your message subject"; // Create the body of your message. var body = new TextPart() { Text = "Your message text" }; // Create a PDF attachment to send the generated letter. var attachment = new MimePart("application", "pdf") { Content = new MimeContent(attachmentStream, ContentEncoding.Default), ContentDisposition = new ContentDisposition(ContentDisposition.Attachment), ContentTransferEncoding = ContentEncoding.Base64, FileName = "attachment.pdf" }; var multipart = new Multipart("mixed"); multipart.Add(body); multipart.Add(attachment); message.Body = multipart; return message; } void SendMessage(MimeMessage message) { using (var client = new SmtpClient()) { // Connect to your SMTP server to send the message. // Use one of these ports: 25, 465, 587, or 2525. client.Connect("MAIL_SERVER", 587, false); // Use the code below if your SMTP server requires authentication. client.Authenticate("USERNAME", "PASSWORD"); try { // Send the message. client.Send(message); } catch (Exception e) { } finally { client.Disconnect(true); } } } }
Your Feedback Matters
As always, we welcome your feedback. Should you have any questions about this examples, feel free to comment below.