Create and Save a Word file with ASP.NET Core

Gembox.Document supports the .NET Standard. As a result of this, various platforms are supported such as .NET Framework, .NET Core, Xamarin, and Universal Windows Platform (UWP).

This allows us to create different kinds of application using, for example ASP.NET, Windows.Forms, or WPF, and run them on different operating systems including Windows, Linux, and MacOS.

The following example is a simple ASP.NET Core MVC application that exports data to specified output file format.

Screenshot
Save data to file in ASP.NET Core MVC Screenshot

See the full code below.

@using System.Globalization
@model Document.Samples.Core.Controllers.InvoiceModel

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <meta charset="utf-8" />
    <title>Create an Excel Workbook</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <style type="text/css">
        thead tr {
            text-align: center;
        }

        .table th {
            color: white;
            background-color: rgb(242, 101, 34);
            padding: 0.5rem;
        }

        .table td {
            padding: 0.5rem;
        }

        .table input {
            font-size: 0.9rem;
            padding: 0.3rem 0.35rem;
        }

        .center-column {
            text-align: center;
        }

        .right-column {
            text-align: right;
        }

        .col-form-label {
            font-weight: bold;
        }

        input.form-control, select.form-control {
            font-size: 0.9rem;
        }

        .form-group > label:after {
            content: ": ";
        }

        .last-row {
            font-weight: bold;
        }
    </style>
</head>
<body style="padding:20px; font-size: 0.9rem">
    <form asp-action="Create">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group row">
            <label asp-for="Number" class="col-md-2 col-sm-3 col-form-label"></label>
            <div class="col-md-5 col-sm-7">
                <input asp-for="Number" type="text" class="form-control" readonly />
            </div>
        </div>
        <div class="form-group row">
            <label asp-for="Date" class="col-md-2 col-sm-3 col-form-label"></label>
            <div class="col-md-5 col-sm-7">
                <input asp-for="Date" type="date" class="form-control" />
            </div>
        </div>
        <div class="form-group row">
            <label asp-for="Name" class="col-md-2 col-sm-3 col-form-label"></label>
            <div class="col-md-5 col-sm-7">
                <input asp-for="Name" class="form-control" />
            </div>
        </div>
        <div class="form-group row">
            <label asp-for="Address" class="col-md-2 col-sm-3 col-form-label"></label>
            <div class="col-md-5 col-sm-7">
                <input asp-for="Address" class="form-control" />
            </div>
        </div>
        <div class="form-group row">
            <label asp-for="Country" class="col-md-2 col-sm-3 col-form-label"></label>
            <div class="col-md-5 col-sm-7">
                @Html.DropDownListFor(m => m.Country, Model.Countries, null, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group row">
            <label asp-for="ContactPerson" class="col-md-2 col-sm-3 col-form-label"></label>
            <div class="col-md-5 col-sm-7">
                <input asp-for="ContactPerson" class="form-control" />
            </div>
        </div>

        <table class="table table-bordered table-condensed">
            <colgroup>
                <col style="width: 30%" />
                <col style="width: 20%" />
                <col style="width: 25%" />
                <col style="width: 25%" />
            </colgroup>
            <thead>
                <tr>
                    <th>Date</th>
                    <th>Work Hours</th>
                    <th>Hour price [USD]</th>
                    <th>Total [USD]</th>
                </tr>
            </thead>
            <tbody>
                @for (int i = 0; i < Model.Items.Count; i++)
                {
                    <tr>
                        <td><input asp-for="Items[i].Date" type="date" class="form-control center-column" /></td>
                        <td><input asp-for="Items[i].Hours" type="number" class="form-control center-column" min="1" max="8" /></td>
                        <td><input asp-for="Items[i].PriceText" type="text" class="form-control-plaintext right-column" readonly /></td>
                        <td><input asp-for="Items[i].TotalText" type="text" class="form-control-plaintext right-column" readonly /></td>
                    </tr>
                }

                <tr class="last-row">
                    <td colspan="3" class="right-column">Grand total:</td>
                    <td id="grandTotal" class="right-column">@Model.GrandTotalText</td>
                </tr>
            </tbody>
        </table>
        <div class="form-group">
            <label asp-for="Notes" style="font-weight: bold"></label>
            <textarea asp-for="Notes" class="form-control"></textarea>
        </div>
        <div>
            <h4>Output format:</h4>
            <div class="form-check">
                <input id="DOCX" name="SelectedFormat" type="radio" class="form-check-input" checked value="DOCX" />
                <label for="DOCX" class="form-check-label">DOCX</label>
            </div>
            <div class="form-check">
                <input id="HTML" name="SelectedFormat" type="radio" class="form-check-input" value="HTML" />
                <label for="HTML" class="form-check-label">HTML</label>
            </div>
            <div class="form-check">
                <input id="RTF" name="SelectedFormat" type="radio" class="form-check-input" value="RTF" />
                <label for="RTF" class="form-check-label">RTF</label>
            </div>
            <div class="form-check">
                <input id="TXT" name="SelectedFormat" type="radio" class="form-check-input" value="TXT" />
                <label for="TXT" class="form-check-label">TXT</label>
            </div>
        </div>
        <hr />
        <button type="submit" class="btn btn-default">Generate</button>
    </form>

    <script type="text/javascript">
        $("input[id$='__Hours']").change(function () {
            var prefix = $(this).attr("id").substring(0, 7);
            var value = parseFloat($(this).val()) * parseFloat($("input[id$='" + prefix + "__PriceText']").val());

            $("input[id$='" + prefix + "__TotalText']").val(value.toFixed(2));

            var grandTotal = 0.0;
            $("input[id$='__TotalText']").each(function () {
                grandTotal += parseFloat($(this).val());
            });

            $("#grandTotal").text(grandTotal.toFixed(2));
        });
    </script>
</body>
</html>
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using GemBox.Document;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace Document.Samples.Core.Controllers
{
    public class DocumentController : Controller
    {
        private static readonly IList<InvoiceItemModel> data = new List<InvoiceItemModel>()
        {
            new InvoiceItemModel() { Date = DateTime.UtcNow.AddDays(-4), Hours = 8, Price = 35.0 },
            new InvoiceItemModel() { Date = DateTime.UtcNow.AddDays(-3), Hours = 8, Price = 35.0 },
            new InvoiceItemModel() { Date = DateTime.UtcNow.AddDays(-2), Hours = 6, Price = 35.0 },
            new InvoiceItemModel() { Date = DateTime.UtcNow.AddDays(-1), Hours = 5, Price = 35.0 },
        };

        private static readonly SelectListItem[] countries = CultureInfo.GetCultures(CultureTypes.SpecificCultures)
            .Select(c => new RegionInfo(c.LCID).EnglishName)
            .Distinct()
            .OrderBy(k => k)
            .Select(k => new SelectListItem() { Text = k, Value = k })
            .ToArray();

        private static int invoiceNumber = 1;
        private IHostingEnvironment environment;

        public DocumentController(IHostingEnvironment environment)
        {
            this.environment = environment;
        }

        private static SaveOptions GetSaveOptions(string format)
        {
            switch (format.ToUpperInvariant())
            {
                case "DOCX":
                    return SaveOptions.DocxDefault;
                case "HTML":
                    return SaveOptions.HtmlDefault;
                case "RTF":
                    return SaveOptions.RtfDefault;
                case "TXT":
                    return SaveOptions.TxtDefault;
                default:
                    throw new NotSupportedException("Format '" + format + "' is not supported.");
            }
        }

        private static byte[] GetBytes(DocumentModel document, SaveOptions options)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                document.Save(stream, options);
                return stream.ToArray();
            }
        }

        private DocumentModel Process(InvoiceModel model)
        {
            string path = Path.Combine(this.environment.ContentRootPath, "Invoice.docx");

            // Load template document
            DocumentModel document = DocumentModel.Load(path);

            // Subscribe to FieldMerging event (we want to format the output)
            document.MailMerge.FieldMerging += (sender, e) =>
            {
                if (e.IsValueFound)
                    switch (e.FieldName)
                    {
                        case "Date":
                            ((Run)e.Inline).Text = ((DateTime)e.Value).ToString("dddd, MMMM d, yyyy", CultureInfo.InvariantCulture);
                            break;
                        case "Price":
                        case "Total":
                        case "GrandTotal":
                            ((Run)e.Inline).Text = InvoiceModel.FormatNumber((double)e.Value);
                            break;
                    }
            };

            // Fill table and grand total
            document.MailMerge.Execute(model.Items, "Item");
            document.MailMerge.Execute(model);

            // Fill invoice data
            document.MailMerge.Execute(
                new
                {
                    Number = model.Number,
                    InvoiceDate = model.Date.ToShortDateString()
                });


            // Fill customer data
            document.MailMerge.Execute(
                new Dictionary<string, object>()
                {
                    { "Name", model.Name },
                    { "Address" , model.Address },
                    { "Country" , model.Country },
                    { "ContactPerson" , model.ContactPerson }
                });

            // Fill notes
            document.MailMerge.Execute(
                new KeyValuePair<string, object>[]
                {
                    new KeyValuePair<string, object>("Notes", model.Notes)
                });


            invoiceNumber = invoiceNumber == int.MaxValue ? 1 : invoiceNumber + 1;

            return document;
        }

        public IActionResult Create()
        {
            return View(new InvoiceModel()
            {
                Number = invoiceNumber,
                Date = DateTime.UtcNow,
                Name = "ACME Corp.",
                Address = "240 Old Country Road, Springfield, IL",
                Countries = countries,
                Country = "United States",
                ContactPerson = "Joe Smith",
                Items = data,
                Notes = "Payment via check.",
                SelectedFormat = "DOCX"
            });
        }

        [HttpPost]
        public ActionResult Create(InvoiceModel model)
        {
            ComponentInfo.SetLicense(DocumentLicense.Value);

            if (!ModelState.IsValid)
                return View(model);

            SaveOptions options = GetSaveOptions(model.SelectedFormat);
            DocumentModel document = this.Process(model);

            return File(GetBytes(document, options), options.ContentType, "Create." + model.SelectedFormat.ToLowerInvariant());
        }
    }

    public class InvoiceModel
    {
        public int Number { get; set; }
        public DateTime Date { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
        public IList<SelectListItem> Countries { get; set; }
        public string Country { get; set; }
        public IList<InvoiceItemModel> Items { get; set; }
        public string Notes { get; set; }
        public string SelectedFormat { get; set; }

        [Display(Name = "Contact person")]
        public string ContactPerson { get; set; }

        public static string FormatNumber(double value)
        {
            return value.ToString("F2", CultureInfo.InvariantCulture);
        }

        public double GrandTotal
        {
            get { return this.Items.Select(i => i.Total).Sum(); }
        }

        public string GrandTotalText
        {
            get { return FormatNumber(this.GrandTotal); }
        }
    }

    public class InvoiceItemModel
    {
        public DateTime Date { get; set; }
        public int Hours { get; set; }
        public double Price { get; set; }

        public string PriceText
        {
            get { return InvoiceModel.FormatNumber(this.Price); }
            set { this.Price = double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out double result) ? result : 0.0; }
        }

        public double Total
        {
            get { return this.Hours * this.Price; }
        }

        public string TotalText
        {
            get { return InvoiceModel.FormatNumber(this.Hours * this.Price); }
        }
    }
}