Word (RTF) Mail Merge

This demo illustrates how to use the mail merge functionality to generate personalized letters with the help of the Word Processing Document API. Select a sender (Employee), an addressee (Customer), and a document format to export the result. Click Download to save the document to your computer.

Options
Employee Id
Customer Id
File Format
@model AspNetCoreDemos.OfficeFileAPI.WordRTFMailMergeModel
@using DevExtreme.AspNet.Mvc

@using (Html.BeginForm("WordRTFMailMergeExportTo", "MailMerge", FormMethod.Post)) {
    <script type="text/javascript">
        function UpdatePreview() {
            var EmployeeIdValue = $("#EmployeesBox").dxSelectBox('instance').option('value');
            var CustomerIdValue = $("#CustomersBox").dxSelectBox('instance').option('value');
            var args = "EmployeeID=" + encodeURIComponent(EmployeeIdValue) +
                "&CustomerID=" + encodeURIComponent(CustomerIdValue);
            WordRTFPreview.Update(args);
        }
    </script>

    <div class="demo-view-container">
        @await Html.PartialAsync("WordRTFPreviewPartial", Model.PreviewModel)
    </div>

    <div class="options">
        <div class="caption">Options</div>
        <div class="option">
            <div class="label">Employee Id</div>
            @(Html.DevExtreme().SelectBoxFor(m => m.EmployeeId)
                               .ID("EmployeesBox")
                               .DataSource(Model.Employees
                               .Select(i => new { Value = i.EmployeeID, Text = i.FirstName + " " + i.LastName }))
                               .ValueExpr("Value")
                               .DisplayExpr("Text")
                               .OnValueChanged("UpdatePreview")
            )
        </div>
        <div class="option">
            <div class="label">Customer Id</div>
            @(Html.DevExtreme().SelectBoxFor(m => m.CustomerId)
                               .ID("CustomersBox")
                               .DataSource(Model.Customers
                               .Select(i => new { Value = i.CustomerID, Text = i.ContactName }))
                               .ValueExpr("Value")
                               .DisplayExpr("Text")
                               .OnValueChanged("UpdatePreview")
            )
        </div>
        @await Html.PartialAsync("WordRTFDocumentDownloaderPartial", Model)
    </div>
}
@model AspNetCoreDemos.OfficeFileAPI.WordRTFPreviewModel

<iframe id="previewFrame" src="@Url.Action(Model.PreviewDocumentAction, Model.ControllerName)" height="@Model.IFrameSize" class="demo-preview-border" style="width:100%;box-sizing:border-box"></iframe>

<script type="text/javascript">
    WordRTFPreview = {
        basePath: '@Url.Action(Model.PreviewDocumentAction, Model.ControllerName)',
        Update: function (param) {
            var iframeElementName = "previewFrame";
            var iframeElement = document.getElementById(iframeElementName);
            if (!iframeElement)
                return;
            var additionalParams = "&" + new Date().valueOf();
            if (param)
                additionalParams = param;
            iframeElement.src = this.basePath + "?" + additionalParams;
        }
    };
</script>
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreDemos.OfficeFileAPI {
    public partial class MailMergeController : OfficeDemoController {

        public IActionResult WordRTFMailMerge() {
            WordRTFMailMergeModel model = new WordRTFMailMergeModel();
            return View(model);
        }

    }
}
using System.Data;
using System.IO;
using DevExpress.Office.Services;
using DevExpress.Web.Office;
using DevExpress.XtraRichEdit;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreDemos.OfficeFileAPI {
    public partial class MailMergeController : OfficeDemoController {
        const string mailMergeTemplateFilePath = "/Documents/MailMerge.rtf";

        public IActionResult WordRTFPreviewMailMerge(WordRTFMailMergeModel model) {
            Stream stream = ExecuteMerge(DocumentFormat.Html, model.Employee, model.Customer);
            return CreatePreviewResult(stream);
        }

        public IActionResult WordRTFMailMergeExportTo(WordRTFMailMergeModel model) {
            RichEditFileFormat documentType = model.FileFormat;
            DocumentFormat documentFormat = WordRTFUtils.ConvertToFormat(documentType);
            Stream stream = ExecuteMerge(documentFormat, model.Employee, model.Customer);
            if (stream == null)
                return new EmptyResult();
            string contentType = WordRTFUtils.ConvertToContentType(documentType);
            string fileExtension = WordRTFUtils.ConvertToFileExtension(documentType);
            return CreateFileStreamResult(stream, contentType, fileExtension);
        }

        Stream ExecuteMerge(DocumentFormat documentFormat, Employee employee, Customer customer) {
            DataTable mailMergeDataTable = new DataTable();
            AddDataColumns(mailMergeDataTable);

            RichEditDocumentServer documentServer = new RichEditDocumentServer();
            IUriStreamService uriService = (IUriStreamService)documentServer.GetService(typeof(IUriStreamService));
            uriService.RegisterProvider(new WordRTFMailMergeDBUriStreamProvider(mailMergeDataTable, "Photo", this));

            Stream result = null;
            if (!PutMergeData(mailMergeDataTable, employee, customer))
                return null;
            string filePath = HostingEnvironment.ContentRootPath + mailMergeTemplateFilePath;
            documentServer.LoadDocument(filePath);
            documentServer.Options.MailMerge.DataSource = mailMergeDataTable;
            documentServer.Options.MailMerge.ViewMergedData = true;
            documentServer.Options.Export.Html.EmbedImages = true;
            result = new MemoryStream();
            if (documentFormat == DocumentFormat.Undefined) {
                documentServer.Options.MailMerge.ActiveRecord = 0;
                documentServer.ExportToPdf(result);
            }
            else {
                DevExpress.XtraRichEdit.API.Native.MailMergeOptions options = documentServer.CreateMailMergeOptions();
                options.DataSource = mailMergeDataTable;
                options.FirstRecordIndex = 0;
                options.LastRecordIndex = 0;
                documentServer.MailMerge(options, result, documentFormat);
            }
            result.Seek(0, SeekOrigin.Begin);
            return result;
        }

        void AddDataColumns(DataTable mailMergeDataTable) {
            mailMergeDataTable.Columns.Add("EmployeeID", typeof(int));
            mailMergeDataTable.Columns.Add("FirstName", typeof(string));
            mailMergeDataTable.Columns.Add("LastName", typeof(string));
            mailMergeDataTable.Columns.Add("ContactName", typeof(string));
            mailMergeDataTable.Columns.Add("Employees.Address", typeof(string));
            mailMergeDataTable.Columns.Add("Employees.City", typeof(string));
            mailMergeDataTable.Columns.Add("Employees.PostalCode", typeof(string));
            mailMergeDataTable.Columns.Add("ContactTitle", typeof(string));
            mailMergeDataTable.Columns.Add("CompanyName", typeof(string));
            mailMergeDataTable.Columns.Add("Customers.Address", typeof(string));
            mailMergeDataTable.Columns.Add("Customers.City", typeof(string));
            mailMergeDataTable.Columns.Add("Customers.PostalCode", typeof(string));
            mailMergeDataTable.Columns.Add("Title", typeof(string));
            mailMergeDataTable.Columns.Add("Photo", typeof(object));
        }

        bool PutMergeData(DataTable mailMergeDataTable, Employee employee, Customer customer) {
            mailMergeDataTable.Rows.Clear();
            mailMergeDataTable.Rows.Add(employee.EmployeeID,
                                        employee.FirstName,
                                        employee.LastName,
                                        customer.ContactName,
                                        employee.Address,
                                        employee.City,
                                        employee.PostalCode,
                                        customer.ContactTitle,
                                        customer.CompanyName,
                                        customer.Address,
                                        customer.City,
                                        customer.PostalCode,
                                        employee.Title,
                                        null);
            return true;
        }
    }

    public class WordRTFMailMergeDBUriStreamProvider : IUriStreamProvider {
        static readonly string prefix = "dbimg://";
        DataTable table;
        string columnName;
        MailMergeController pathMapper;

        public WordRTFMailMergeDBUriStreamProvider(DataTable table, string columnName, MailMergeController controller) {
            this.table = table;
            this.columnName = columnName;
            this.pathMapper = controller;
        }

        Stream IUriStreamProvider.GetStream(string uri) {
            uri = uri.Trim();
            if (!uri.StartsWith(prefix))
                return null;
            string strId = uri.Substring(prefix.Length).Trim();
            int id;
            if (!int.TryParse(strId, out id))
                return null;
            string pictureRoot = pathMapper.HostingEnvironment.ContentRootPath + "/Documents/";
            string fileName = string.Format("{0}Photo{1}.jpeg", pictureRoot, id);
            byte[] bytes = null;
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) {
                int length = (int)fs.Length;
                bytes = new byte[length];
                fs.Read(bytes, 0, length);
            }
            return new MemoryStream(bytes);
        }
    }
}
using System.Collections.Generic;

namespace AspNetCoreDemos.OfficeFileAPI {
    public class WordRTFMailMergeModel : WordRTFModelBase {
        public WordRTFMailMergeModel() {
            PreviewModel.PreviewDocumentAction = "WordRTFPreviewMailMerge";
            PreviewModel.ControllerName = "MailMerge";
            Data = new InMemoryNWindData();
            Customers = Data.Customers;
            Employees = Data.Employees;
            EmployeeId = Employees[0].EmployeeID;
            CustomerId = Customers[0].CustomerID;
        }

        public Employee Employee { get { return Employees.Find(item => item.EmployeeID == EmployeeId); } }
        public Customer Customer { get { return Customers.Find(item => item.CustomerID == CustomerId); } }
        public int EmployeeId { get; set; }
        public string CustomerId { get; set; }
        public List<Customer> Customers { get; private set; }
        public List<Employee> Employees { get; private set; }
        public InMemoryNWindData Data { get; set; }
    }
}
namespace AspNetCoreDemos.OfficeFileAPI {
    public class WordRTFModelBase {
        public WordRTFModelBase() {
            PreviewModel = new WordRTFPreviewModel();
            PreviewModel.OwnerPropertyName = "PreviewModel";
            FileFormat = RichEditFileFormat.Rtf;
        }

        public RichEditFileFormat FileFormat { get; set; }
        public WordRTFPreviewModel PreviewModel { get; internal set; }
    }

    public class WordRTFPreviewModel {
        public WordRTFPreviewModel() {
        }

        public string OwnerPropertyName { get; set; }
        public string PreviewDocumentAction { get; set; }
        public string ControllerName { get; set; }
        public int IFrameSize { get; set; } = 452;
    }
}