Create and save Word file (DOCX) in ASP.NET Core

GemBox.Document is a standalone .NET component that's ideal for web applications (like ASP.NET Core, ASP.NET MVC and ASP.NET Web Forms) because of its fast performance and thread safety when working with multiple DocumentModel objects.

Gembox.Document provides support for .NET Standard which is why it can be used on various .NET platforms such as .NET Framework, .NET Core, Xamarin and Universal Windows Platform (UWP).

The following example demonstrates how you can create a simple ASP.NET Core MVC application that imports data from a web form to a template document using mail merge, creates a Word file of a specified format (DOCX, RTF, XML or HTML) and downloads it to the client's browser.

Screenshot of submitted web form Razor view in ASP.NET Core MVC
Sending form data from Razor view in ASP.NET Core MVC demo application
Screenshot of created Word file of DOCX format in ASP.NET Core MVC
Generated DOCX file from template Word document in ASP.NET Core MVC demo application
@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 class="form-check">
                <input id="PDF" name="SelectedFormat" type="radio" class="form-check-input" value="PDF" />
                <label for="PDF" class="form-check-label">PDF</label>
            </div>
            <div class="form-check">
                <input id="XPS" name="SelectedFormat" type="radio" class="form-check-input" value="XPS" />
                <label for="XPS" class="form-check-label">XPS</label>
            </div>
            <div class="form-check">
                <input id="BMP" name="SelectedFormat" type="radio" class="form-check-input" value="BMP" />
                <label for="BMP" class="form-check-label">BMP</label>
            </div>
            <div class="form-check">
                <input id="GIF" name="SelectedFormat" type="radio" class="form-check-input" value="GIF" />
                <label for="GIF" class="form-check-label">GIF</label>
            </div>
            <div class="form-check">
                <input id="JPG" name="SelectedFormat" type="radio" class="form-check-input" value="JPG" />
                <label for="JPG" class="form-check-label">JPG</label>
            </div>
            <div class="form-check">
                <input id="PNG" name="SelectedFormat" type="radio" class="form-check-input" value="PNG" />
                <label for="PNG" class="form-check-label">PNG</label>
            </div>
            <div class="form-check">
                <input id="TIF" name="SelectedFormat" type="radio" class="form-check-input" value="TIF" />
                <label for="TIF" class="form-check-label">TIF</label>
            </div>
            <div class="form-check">
                <input id="WMP" name="SelectedFormat" type="radio" class="form-check-input" value="WMP" />
                <label for="WMP" class="form-check-label">WMP</label>
            </div>
        </div>
        <hr />
        <button type="submit" class="btn btn-default">Generate</button>
    </form>

    <script>
        $("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;

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;
            case "PDF":
                return SaveOptions.PdfDefault;
            case "XPS":
                return SaveOptions.XpsDefault;
            case "BMP":
                return new ImageSaveOptions() { Format = ImageSaveFormat.Bmp };
            case "GIF":
                return new ImageSaveOptions() { Format = ImageSaveFormat.Gif };
            case "JPG":
                return new ImageSaveOptions() { Format = ImageSaveFormat.Jpeg };
            case "PNG":
                return new ImageSaveOptions() { Format = ImageSaveFormat.Png };
            case "TIF":
                return new ImageSaveOptions() { Format = ImageSaveFormat.Tiff };
            case "WMP":
                return new ImageSaveOptions() { Format = ImageSaveFormat.Wmp };
            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("FREE-LIMITED-KEY");

        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); }
    }
}

GemBox.Document is only licensed per developer and the licenses include a royalty-free deployment. There are no server or OEM licenses, there are no additional costs for anything (like building, testing and deploying).

So, you're free to build an unlimited number of applications and deploy or distribute them to an unlimited number of servers or end user machines with no extra cost.

Check next example or download examples from GitHub.