Using the DocX DLL to Programmatically Create Word Documents

Introduction

The attached example demonstrates the use of Cathal Coffey's "DocX" DLL which can be found on CodePlex here: http://docx.codeplex.com/.  The project offers a DLL which can be downloaded and added to a project and then used to programmatically create Word documents on the fly and without any reliance upon the Microsoft Office DLLs. I noticed that an update to the project broke the invoice project example that I was using and I had to modify it to get it up and running again. You might find the example useful if you need to generate Word documents programmatically and if you also found the example failing following the update.

The Invoice example is a great introduction to the use of the DLL as it demonstrates a number of common tasks:

  • Programmatic creation of a template document
  • Programmatic population of the template coupled with renaming and saving the document
  • Adding graphics to the page layout (a company icon in this case)
  • Populating fields created as custom properties in the document
  • Creating and populating tables and inserting them into the document

Much of the replacement invoice project does essentially the same thing as the original version although I altered the appearance of the document. This example does correct the issues I found with the example that I originally downloaded from the CodePlex project site.

DocX-DLL.jpg

Figure 1 - Generated Invoice Document

Attached Project

The attached project contains a single example; it is an updated version of the invoice example provided on the CodePlex project site. This updated version corrects the failed parts of the example I encountered and I have updated the comments to hopefully better describe what the code is doing at any given point in the document generation process.

The project itself is a simple console mode application that generates a fake invoice based upon a collection of canned data used to simulate acquiring the data from a database. Whilst running, the code checks to see if a document template exists (and this template is actually just a standard Word document and not really a template file). If the template document is not found, the code creates one programmatically and saves it.

Continuing on, the code next recovers data from the data source (in this case, a collection of dummy data). That recovered data is then used to replace the default property values stored in the template with live content. Once populated with data, the code lastly saves the new template based document with a canned file name. Given the design of the original project, one may look into the bin folder to locate both the document template and, after running the code, the populated document with an alternative file name so as to not overwrite the template itself. Figure 1 in this document provides a screen shot of the generated document.

The code is fully annotated and you can see what is going on by reading the comments embedded within the code. You may find it more convenient to open the project and review the code and comments from within Visual Studio 2012.

using System;
using System.Linq;
using System.IO;
using System.Data;
using System.Drawing;
using System.Reflection;
using System.IO;
namespace Novacode
{
    /// <summary>
    /// This class is an updated, alternate version of the
    /// example project found on the project's website
    /// at http://docx.codeplex.com/; that project was
    /// written and maintained by Cathal Coffey.
    ///
    /// I rewrote the example after noticing that the
    /// invoice example did not work correctly following 
    /// an update of the DocX DLL.  Other than some
    /// minor changes, the example provided herein is the
    /// work of the original author of the DLL, Cathal Coffey,
    /// I take no credit for Mr. Coffey's work.
    ///
    /// The DocX DLL offers a terrific way to generate
    /// Word documents programatically and without making
    /// use of the Microsoft Office DLLs.  If one needed
    /// to programmatically create Word documents and
    /// attach those documents to an email message for
    /// an automated distribution (such as weekly billing
    /// invoices, or any sort of management rollup or report),
    /// this DLL is a very good way to get the job done,
    /// particularly if you are working with a constrained
    /// budget and lack access to the traditional
    /// enterprise products providing similar functionality.
    /// </summary>
    class Program
    {
        // reference to the executing assembly - will
        // be used to obtain embedded resources per
        // the original example. 
        static Assembly gAssembly;
        // reference to the working document.
        static DocX gDocument;
        static void Main(string[] args)
        {
            // set a global reference to the executing assembly
            gAssembly = Assembly.GetExecutingAssembly();
            // To begin with, we can look for a document template 
            // to see if it exists, if it does not, we will 
            // programmatically create the template, else we will use it.
            try
            {
                if (File.Exists(@"InvoiceTemplate.docx"))
                {
                    // set a global reference to the template;
                    // note the template is just a document and
                    // not actually a template
                    gDocument = DocX.Load(@"InvoiceTemplate.docx");
                    // once we have the document, we will populate it
                    // in code, this will create the document we want
                    // to send out or print.
                    gDocument =
                        CreateInvoiceFromTemplate(DocX.Load(@"InvoiceTemplate.docx"));
                    // Save the current working document so as not
                    // to overwrite the template document that we
                    // will want to reuse many times
                    gDocument.SaveAs("NewLoadedShipment.docx");
                }
                else
                {
                    // Create a store a global reference to the
                    // template 'InvoiceTemplate.docx'.
                    gDocument = CreateInvoiceTemplate();
                    // Save the template 'InvoiceTemplate.docx'.
                    gDocument.Save();
                    // I will be lazy and just call the
                    // same code again now that we have
                    // a working template saved and so
                    // the user does not have to restart
                    // the appication is the template was
                    // originally missing
                    // set a global reference to the template
                    // just created
                    gDocument = DocX.Load(@"InvoiceTemplate.docx");
                    // populate the document with data so we
                    // can print it or email it off to a
                    // recipient
                    gDocument = CreateInvoiceFromTemplate(DocX.Load(@"InvoiceTemplate.docx"));
                    // Save the current working document so as not
                    // to overwrite the template document that we
                    // will want to reuse many times
                    gDocument.SaveAs("NewLoadedShipment.docx");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("An Error has occurred");
                Console.WriteLine("Message:  " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Console.WriteLine("Press any key to continue");
                Console.Read();
            }
        }
        /// <summary>
        /// Take the document template and populate it with
        /// variable data to ready it for one specific
        /// billing target
        ///
        /// This addresses putting custom data into various
        /// regions of the document, demonstrating how to
        /// fully populate the template.  Examine each
        /// defined region to see how to put in text, images,
        /// and tabular data.
        /// </summary>
        /// <param name="template"></param>
        /// <returns></returns>
        private static DocX CreateInvoiceFromTemplate(DocX template)
        {
            #region Logo
            // A glance at the template shows us that
            // the logo exists in a table at row 0, cell 1.
            // That image is a placeholder image that has
            // to be replaced with the actual logo.
            Paragraph logo_paragraph =
                template.Tables[0].Rows[0].Cells[1].Paragraph;
            // Prepare to replace the temporary logo in
            // the template with the actual logo by first
            // removing that temporary image from the
            // template
            logo_paragraph.Pictures[0].Remove();
            // Add the actual logo to this document
            // which is contained in an embedded
            // resource
            Novacode.Image logo =
                template.AddImage(
                gAssembly.GetManifestResourceStream("Novacode.Resources.FakeLogo.png"));
            // Insert the extracted logo into the paragraph
            Picture logo_picture = logo_paragraph.InsertPicture(logo.Id);
            #endregion
            #region Set Custom Property values
            // Set the value of the custom property 'company_name'.
            template.AddCustomProperty(
                new CustomProperty("company_name", "Bart's Parts"));
            // Set the value of the custom property 'company_slogan'.
            template.AddCustomProperty(
                new CustomProperty("company_slogan", "The King of OEM distributions"));
            // Set the value of the custom properties 'hired_company_address_line_one',
            //'hired_company_address_line_two' and 'hired_company_address_line_three'.
            template.AddCustomProperty(
                new CustomProperty("hired_company_username", "Joe Bagadonuts"));
            template.AddCustomProperty(
                new CustomProperty("hired_company_address_line_one", "1100 Main Street"));
            template.AddCustomProperty(
                new CustomProperty("hired_company_address_line_two", "Atlanta, GA"));
            template.AddCustomProperty(
                new CustomProperty("hired_company_address_line_three", "30303"));
            // Set the value of the custom property 'invoice_date'.
            template.AddCustomProperty(
                new CustomProperty("invoice_date", DateTime.Today.Date.ToShortDateString()));
            // Set the value of the custom property 'invoice_time'.
            template.AddCustomProperty(
                new CustomProperty("invoice_time", DateTime.Today.Date.ToShortTimeString()));
            // Set the value of the custom property
            // 'hired_company_details_line_one' and 'hired_company_details_line_two'.
            template.AddCustomProperty(
                new CustomProperty("hired_company_details_line_one",
                    "1100 North Main Street, Fremont, CA 12345"));
            template.AddCustomProperty(
                new CustomProperty("hired_company_details_line_two",
                    "Phone: 012-345-6789, Fax: 012-345-6789, e-mail: [email protected]"));
            #endregion
            #region Replace Placeholder Table
            // Capture the blank table in the template
            Table t = template.Tables[1];
            // replace the blank table with a table containing
            // the invoice data
            Table invoice_table =
                CreateAndInsertInvoiceTableAfter(t, ref template);
            // remove the blank table from the document
            t.Remove();
            // Return the template now that it has been
            // modified to hold all of our custom data.
            return template;
            #endregion
        }
        /// <summary>
        /// Create a template
        ///
        /// This method is called whenever the template does not
        /// exist.  The purpose of the method is to create the
        /// template so that it might be used as the basis for
        /// creating data specific versions of the invoice
        /// document
        ///
        /// In general, the template defines locations and
        /// custom properties that will be used by the process
        /// used to populate the document with actual data.
        ///
        /// In that process, the code will find each specific
        /// location and property and replace the default
        /// text assigned here with actual information
        /// </summary>
        /// <returns></returns>
        private static DocX CreateInvoiceTemplate()
        {
            // Create a new document with the canned
            // document title.  Note this is really just
            // a document and not an actual template
            DocX document = DocX.Create(@"InvoiceTemplate.docx");
            // Create a table for layout purposes
            // (This table will be invisible).
            // Document content will be placed into various cells
            // within this table
            Table layout_table = document.InsertTable(2, 2);
            layout_table.Design = TableDesign.TableNormal;
            layout_table.AutoFit = AutoFit.Window;
            #region Create document style
            // create formatting styles that will be used
            // to define the appearance of the document
            // once populated with actual data
            // Large Dark formatting - for titles
            Formatting large_dark_formatting = new Formatting();
            large_dark_formatting.Bold = true;
            large_dark_formatting.Size = 16;
            large_dark_formatting.FontColor = Color.Black;
            // Dark formatting
            Formatting dark_formatting = new Formatting();
            dark_formatting.Bold = true;
            dark_formatting.Size = 12;
            dark_formatting.FontColor = Color.Black;
            // Light formatting
            Formatting light_formatting = new Formatting();
            light_formatting.Italic = true;
            light_formatting.Size = 11;
            light_formatting.FontColor = Color.Black;
            #endregion
            #region Company Name
            // Define a custom property for the company name, this property
            // will be populated with the actual company name when the
            // document is populated with actual data
            // Capture the upper left Paragraph location in the layout_table; this
            // is the location the company name will be placed into when the
            // document is populated with data.
            Paragraph upper_left_paragraph =
                layout_table.Rows[0].Cells[0].Paragraph;
            // Create a custom property called company_name and set it to
            // display a generic company name in the template
            CustomProperty company_name =
                new CustomProperty("company_name", "Company Name");
            // Put the document property into the table at the
            // correct location and apply the display style
            layout_table.Rows[0].Cells[0].Paragraph.InsertDocProperty(
                company_name, large_dark_formatting);
            // Force the next text insert to be on a new line.
            upper_left_paragraph.InsertText("\n", false);
            #endregion
            #region Company Slogan
            // use the same approach used with the company name to
            // insert a new property under the company name; this
            // property will display the company slogan using a
            // smaller font and in italics
            // Create a custom property called company_slogan
            CustomProperty company_slogan =
                new CustomProperty("company_slogan",
                    "Company slogan goes here.");
            // Insert a field of type doc property
            // (This will display the custom property 'company_slogan')
            upper_left_paragraph.InsertDocProperty(
                company_slogan, light_formatting);
            #endregion
            #region Company Logo
            // Get the upper right Paragraph in the layout_table.
            Paragraph upper_right_paragraph = layout_table.Rows[0].Cells[1].Paragraph;
            // Add a template logo image to this document.
            Novacode.Image logo = document.AddImage(gAssembly.GetManifestResourceStream("Novacode.Resources.logo_template.png"));
            // Insert this template logo into the upper right Paragraph.
            Picture picture_logo = upper_right_paragraph.InsertPicture(logo.Id, "", "");
            upper_right_paragraph.Alignment = Alignment.right;
            #endregion
            #region Hired Company Address
            // Create a custom property called
            // company_address_line_one
            CustomProperty hired_company_username =
                new CustomProperty("hired_company_username",
                    "User Name:");
            // Create a custom property called
            // company_address_line_one
            CustomProperty hired_company_address_line_one =
                new CustomProperty("hired_company_address_line_one",
                    "Street Address,");
            // Get the lower left Paragraph in the layout_table.
            Paragraph lower_left_paragraph =
                layout_table.Rows[1].Cells[0].Paragraph;
            lower_left_paragraph.InsertText("TO:\n", false, dark_formatting);
            // Insert a field of type doc property
            // (This will display the custom property
            // 'hired_company_username')
            lower_left_paragraph.InsertDocProperty(
                hired_company_username, light_formatting);
            // Force the next text insert to be on a new line.
            lower_left_paragraph.InsertText("\n", false);
            // Insert a field of type doc property
            // (This will display the custom property
            // 'hired_company_address_line_one')
            lower_left_paragraph.InsertDocProperty(
                hired_company_address_line_one, light_formatting);
            // Force the next text insert to be on a new line.
            lower_left_paragraph.InsertText("\n", false);
            // Create a custom property called
            // company_address_line_two
            CustomProperty hired_company_address_line_two =
                new CustomProperty("hired_company_address_line_two",
                    "City,");
            // Insert a field of type doc property
            // (This will display the custom property
            // 'hired_company_address_line_two')
            lower_left_paragraph.InsertDocProperty(
                hired_company_address_line_two, light_formatting);
            // Force the next text insert to be on a new line.
            lower_left_paragraph.InsertText("\n", false);
            // Create a custom property called company_address_line_two
            CustomProperty hired_company_address_line_three =
                new CustomProperty("hired_company_address_line_three",
                    "Zip Code");
            // Insert a field of type doc property
            // (This will display the custom property
            // 'hired_company_address_line_three')
            lower_left_paragraph.InsertDocProperty(
                hired_company_address_line_three, light_formatting);
            #endregion
            #region Date & Invoice number
            // Get the lower right Paragraph from the layout table.
            Paragraph lower_right_paragraph =
                layout_table.Rows[1].Cells[1].Paragraph;
            CustomProperty invoice_date =
                new CustomProperty("invoice_date",
                    DateTime.Today.Date.ToString("d"));
            lower_right_paragraph.InsertText("Date: ",
                false, dark_formatting);
            lower_right_paragraph.InsertDocProperty(invoice_date,
                light_formatting);
            CustomProperty invoice_time =
                new CustomProperty("invoice_time",
                    DateTime.Today.Date.ToShortTimeString());
            lower_right_paragraph.InsertText("\nTime: ",
                false, dark_formatting);
            lower_right_paragraph.InsertText("", false,
                light_formatting);
            lower_right_paragraph.InsertDocProperty(invoice_time,
                light_formatting);
            // set the paragraph to align against the right side
            // of the invoice
            lower_right_paragraph.Alignment = Alignment.right;
            #endregion
            #region Statement of thanks
            // Insert an empty Paragraph between two Tables,
            // so that they do not touch.
            document.InsertParagraph(string.Empty, false);
            // This table will hold all of the invoice data.
            // set the table style to a canned format
            Table invoice_table = document.InsertTable(7, 4);
            invoice_table.Design = TableDesign.LightShadingAccent1;
            invoice_table.Alignment = Alignment.center;
            // A nice thank you Paragraph.
            Paragraph thankyou =
                document.InsertParagraph("\nThank you for your business, " +
                "see us again for all of your OEM parts needs.",
                false, dark_formatting);
            thankyou.Alignment = Alignment.center;
            #endregion
            #region Hired company details
            CustomProperty hired_company_details_line_one =
                new CustomProperty("hired_company_details_line_one",
                    "Street Address, City, ZIP Code");
            CustomProperty hired_company_details_line_two =
                new CustomProperty("hired_company_details_line_two",
                    "Phone: 000-000-0000, Fax: 000-000-0000, " +
                    "e-mail: [email protected]");
            Paragraph companyDetails =
                document.InsertParagraph(string.Empty, false);
            companyDetails.InsertDocProperty(hired_company_details_line_one,
                light_formatting);
            companyDetails.InsertText("\n", false);
            companyDetails.InsertDocProperty(hired_company_details_line_two,
                light_formatting);
            companyDetails.Alignment = Alignment.center;
            #endregion
            // Return the template document now that it has been created.
            return document;
        }
        /// <summary>
        /// This method will capture the data required to
        /// populate the invoice's table, and it will then
        /// use that data to populate a new table, will
        /// insert the new table into the document, and
        /// it will then remove the old blank place holder
        /// table from the document
        /// </summary>
        /// <param name="t"></param>
        /// <param name="document"></param>
        /// <returns></returns>
        private static Table CreateAndInsertInvoiceTableAfter(
            Table t, ref DocX document)
        {
            // Grab data from somewhere (Most likely a database); this
            // example just creates a dummy list of values
            DataTable data = GetDataFromDatabase();
            /*
             * The trick to replacing one Table with another,
             * is to insert the new Table after the old one,
             * and then remove the old one.
             */
            Table invoice_table =
                t.InsertTableAfterSelf(data.Rows.Count + 1,
                data.Columns.Count);
            invoice_table.Design = TableDesign.DarkListAccent1;
            #region Table title and column headers
            Formatting table_title = new Formatting();
            table_title.Bold = true;
            invoice_table.Rows[0].Cells[0].Paragraph.InsertText(
                "Invoice ID", false, table_title);
            invoice_table.Rows[0].Cells[0].Paragraph.Alignment =
                Alignment.left;
            invoice_table.Rows[0].Cells[1].Paragraph.InsertText(
                "Part Number", false, table_title);
            invoice_table.Rows[0].Cells[1].Paragraph.Alignment =
                Alignment.left;
            invoice_table.Rows[0].Cells[2].Paragraph.InsertText(
                "Description", false, table_title);
            invoice_table.Rows[0].Cells[2].Paragraph.Alignment =
                Alignment.left;
            invoice_table.Rows[0].Cells[3].Paragraph.InsertText(
                "Unit Price", false, table_title);
            invoice_table.Rows[0].Cells[3].Paragraph.Alignment =
                Alignment.left;
            invoice_table.Rows[0].Cells[4].Paragraph.InsertText(
                "Number Ordered", false, table_title);
            invoice_table.Rows[0].Cells[4].Paragraph.Alignment =
                Alignment.left;
            invoice_table.Rows[0].Cells[5].Paragraph.InsertText(
                "Total", false, table_title);
            invoice_table.Rows[0].Cells[5].Paragraph.Alignment =
                Alignment.left;
            #endregion
            // Loop through the rows in the Table and insert
            // data from the data source.
            for (int row = 1; row < invoice_table.RowCount; row++)
            {
                for (int cell = 0; cell < invoice_table.Rows[row].Cells.Count; cell++)
                {
                    Paragraph cell_paragraph =
                        invoice_table.Rows[row].Cells[cell].Paragraph;
                    cell_paragraph.InsertText(
                        data.Rows[row - 1].ItemArray[cell].ToString(), false);
                }
            }
            // Let the tables coloumns expand to fit its contents.
            invoice_table.AutoFit = AutoFit.Contents;
            // Center the Table
            invoice_table.Alignment = Alignment.center;
            // Return the invloce table now that it has been created.
            return invoice_table;
        }
        /// <summary>
        /// Get the source data and use it to populate the invoice
        /// table displayed in the document
        ///
        /// You will likely be getting the data from a database and
        /// you will have to adjust the column headers and structure
        /// to match the content returned from any required query
        /// against the actual data source.
        /// </summary>
        /// <returns></returns>
        private static DataTable GetDataFromDatabase()
        {
            DataTable table = new DataTable();
            table.Columns.AddRange(new DataColumn[]
            { new DataColumn("InvoiceId"), new DataColumn("PartNo"),
                new DataColumn("Description"), new DataColumn("UnitPrice"),
                new DataColumn("UnitsOrderd"), new DataColumn("RowTotal")});
            table.Rows.Add
            (
                "78123",
                "801-ST344",
                "Steering Column",
                "$287.65",
                "1",
                "$287.65"
            );
            table.Rows.Add
            (
                "78124",
                "71-AC9488",
                "Compressor, AC",
                "$614.82",
                "1",
                "$614.82"
            );
            table.Rows.Add
            (
                "78125",
                "783342",
                "Air filter, Fram",
                "$9.12",
                "1",
                "$9.12"
            );
            table.Rows.Add
            (
                "78126",
                "AC49034",
                "Spark Plug, Platinum",
                "$5.12",
                "8",
                "$40.96"
            );
            table.Rows.Add
             (
                "78127",
                "FMC-66-1243",
                "Bumper, Front",
                "$212.45",
                "1",
                "$212.45"
             );
            table.Rows.Add
             (
                "",
                "",
                "Tax",
                "",
                "",
                "93.20"
             );
            table.Rows.Add
             (
                "",
                "",
                "Total",
                "",
                "",
                "$1258.20"
             );
            return table;
        }
    }
}


Similar Articles