Click to Rate and Give Feedback
MSDN
MSDN Library
Office Development
2007 Office Suites
Technical Articles
 Building Server-Side Document Gener...
Community Content
In this section
Statistics Annotations (0)
Building Server-Side Document Generation Solutions Using the Open XML Object Model (Part 2 of 2)

Summary: In this second part of a two-part series, continue to explore the architecture of a server-side document integration solution. Discover the advantages of creating document packages and manipulating document parts by using the new Open XML object model. (16 printed pages)

Erika Ehrli, Microsoft Corporation

August 2007

Applies to: 2007 Microsoft Office Suites, Microsoft Office Word 2007, Microsoft ASP.NET 2.0, Microsoft Visual Studio 2005, Microsoft SQL Server 2005

Contents

Download the accompanying sample: 2007 Office Sample: Building a Server-Side Document Generation Solution Using the Open XML Object Model.

Read Part 1: Building Server-Side Document Generation Solutions Using the Open XML Object Model (Part 1 of 2).

Overview

The first article in this series, Building Server-Side Document Generation Solutions Using the Open XML Object Model (Part 1 of 2), reviews how to design and build a server-side solution, based on the Microsoft .NET Framework, to assemble Microsoft Office documents by using the Open XML Formats and the Open XML object model. It describes the Open XML Formats architecture and the basic concepts for creating document packages, manipulating document parts, and writing WordprocessingML code by using the Open XML object model. It also explores the architecture of a server-side document integration solution.

This article, Building Server-Side Document Generation Solutions Using the Open XML Object Model (Part 2 of 2), presents a business scenario and explores how to create a sales report in Microsoft Office Word 2007 by using a Microsoft ASP.NET 2.0 application.

The download, 2007 Office Sample: Building a Server-Side Document Generation Solution Using the Open XML Object Model, provides sample files in Microsoft Visual Basic 2005 and Microsoft Visual C# that show how to use the Open XML object model to assemble documents from a server-side application.

Business Scenario: Generating Server-Side Sales Report Summary Documents

Adventure Works Cycles, the fictitious company on which the AdventureWorks sample database is based, is a large, multinational manufacturing company. The company manufactures and sells metal and composite bicycles to North American, European, and Asian commercial markets. The company employs 290 people and is based in Bothell, Washington; several regional sales teams are located throughout the world. Sales-related information is a significant part of the Adventure Works business.

Because of a successful fiscal year, Adventure Works Cycles wants to compensate its sales personnel based on performance. Management needs an overview of the performance of each sales person and has assigned you the task of creating a document that contains each sales person's contact information, total sales for the year, and a breakdown of each sales person's performance by territory. Management wants to compare individual performance with peer performance.

Designing a Sales Report Summary Template

A sample document template is helpful when you design a document integration solution. A template helps you visualize the content and layout of the report you need to assemble.

For the Adventure Works sales report assignment, the document generation solution creates a document that provides the following content and information:

  • Company logo

  • Title: Sales Report for - Employee's Name

  • Contact information

  • Sales summary

  • Sales by territory: A table with all sales person's summary information by territory

In a typical project, this step is optional. Figure 1 shows the document template.

Figure 1. Sales Report Summary document template
Sales Report Summary document template

Designing a template also helps you to understand the structure of the document package, the document parts you create, and the WordprocessingML markup you generate to reproduce the same document programmatically.

After you create the template, you use standard ZIP utilities or package explorer tools such as the Open XML Package Explorer or XMLSpy to explore the structure and required markup of the files stored in a document package.

Building a Server-Side Document Generation Solution

Adventure Works Cycles management is asking you to create an ASP.NET 2.0 application for use on the intranet to allow the view of sales information. Management wants a Web page that allows the selection of an employee and export of a report to Microsoft Office Word 2007 with the sales person's performance information, as shown in Figure 2.

Figure 2. Adventure Works Cycles management ASP.NET 2.0 Web application
Adventure Works Cycles management ASP.NET 2.0 Web

To show how to create an ASP.NET 2.0 Web site that can connect to the Adventure Works line-of-business (LOB) data, and create and export a Word 2007 report, this section includes the following three key steps:

  1. Build the front-end Web server:

    • Create an ASP.NET 2.0 Web site project in Microsoft Visual Studio 2005.

    • Create a Web form for the site that allows a user to select an employee and export a sales summary report to Word 2007.

  2. Add a custom data helper class that connects to the AdventureWorks database server and retrieves sales information.

  3. Add a custom report-generation helper class that generates the Word 2007 sales summary report creation functionality by using the Open XML object model and WordprocessingML.

Building the Front-end Web Server

You can build the front-end Web server of your application by using either ASP.NET 2.0 or Microsoft SharePoint Products and Technologies. In this case, you create the report-generation solution for Adventure Works Cycles by using ASP.NET 2.0 and Visual Studio 2005.

To create the ASP.NET 2.0 Web site project

  1. Start Visual Studio 2005.

  2. On the File menu, point to New, and then click Web Site.

  3. In the New Web Site dialog box, select the ASP.NET Web Site template.

  4. In the Location list, click HTTP or File System.

  5. Specify a path or URL for the Web site or leave the default.

  6. In the Language list, click Visual C# and then click OK.

Visual Studio generates an ASP.NET Web Site project with a single Web form named Default.aspx and a code file named Default.aspx.cs (or Default.aspx.vb if you are using Visual Basic 2005).

Prerequisites

The goal of this sample application is to show you how to generate Word documents by using the Open XML Formats. To build this application, you need the following:

To interact with the Open XML object model, and the System.IO.Packaging namespace, add references to the type libraries.

To add references to the project

  1. In Solution Explorer, expand the References folder.

    NoteNote:

    If you do not see the References folder, on the Project node, click Show All Files.

  2. Right-click the References folder, and then click Add Reference.

  3. Click the Browse tab, and then navigate to and select WindowsBase.dll.

    NoteNote:

    As of the publication of this article, the file is located at local_drive:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0.

  4. Select WindowsBase.dll file, and then click OK.

  5. Right-click the References folder, and then click Add Reference.

  6. Click the Browse tab, navigate to and select the Microsoft.Office.DocumentFormat.OpenXml.dll, and then click OK.

    NoteNote:

    After installing the Microsoft SDK for Open XML Formats Technology Preview, this file is typically located at local_drive:\Program Files\2007 Office System Developer Resources\OpenXMLSDK\1.0.0531\lib.

After adding the references, the Web.config file contains the following markup:

NoteNote:

This code example includes line breaks to facilitate online viewing. You must remove the line breaks before running the code.

Xml
<compilation debug="true">
   <assemblies>
     <add assembly="Microsoft.Office.DocumentFormat.OpenXml, Version=1.0.531.0,
Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
     <add assembly="WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
   </assemblies>
</compilation>
NoteNote:

You do not need to install the 2007 Microsoft Office system on the server to generate Microsoft Office documents. The Open XML Formats enable you to generate and manipulate documents without Automation.

Creating the Web Form

Finally, create a Web form that looks like Figure 1. The Web Form uses a DropDownList control that displays all employee names. The Web Form also has a Button control that allows users to export the sales summary reports by employee to Word 2007.

NoteNote:

This article provides code samples in Microsoft Visual C#. The code sample download that accompanies this article provides code samples in both Visual C# and Microsoft Visual Basic 2005.

To create the Web Form

  • Replace the default.aspx code with the following example code.

    C#
    <%@ Page Language="C#" %>
    <%@ Import Namespace="System.Data" %>
    <%@ Import Namespace="System.Data.SqlClient" %>
    <script runat="server">
        protected void btnExportToWord_Click(object sender, EventArgs e) {
            SalesReportBuilder report = new SalesReportBuilder(ddlEmployee.SelectedItem.Value);
            lblResult.Text = "Report saved at: " + report.CreateDocument();
        }
    </script>
    <html >
    <head runat="server">
        <title>Word 2007 Sales Report Generator</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <img src="Resources/headerImage.gif" style="width: 264px; height: 107px" alt="Adventure Works"/>
                <h1>
                    <span style="font-family: Verdana">Sales Report Generator</span>
                </h1>
                <hr />
                <h3>
                   <span style="font-family: Verdana; color:Maroon;">Create sales report for employee:</span>
                </h3>
                <table border="0" style="font-family: Verdana">
                    <tr>
                        <td>
                            <asp:DropDownList
                            ID="ddlEmployee"
                            runat="server"
                            DataSourceID="EmployeeDataSource"
                            AutoPostBack="True"
                            DataTextField="FullName"
                            DataValueField="SalesPersonID"
                            Width="200px">
                            </asp:DropDownList>
                            <asp:SqlDataSource
                            ID="EmployeeDataSource"
                            runat="server"
                            ConnectionString="<%$ ConnectionStrings:AWConnString %>"
                            SelectCommand="SELECT [SalesPersonID],[FirstName]
                            + ' ' + [LastName] AS [FullName] FROM [Sales].[vSalesPerson]">
                            </asp:SqlDataSource>
                        </td>
                        <td valign="top">
                            <asp:Button
                            ID="btnExportToWord"
                            runat="server"
                            Text="Export to Word 2007"
                            OnClick="btnExportToWord_Click" />
                        </td>
                    </tr>
                </table>
                <br />
                <h3>
                   <span style="font-family: Verdana; color:Blue;">
                      <asp:Label
                      ID="lblResult"
                      runat="server" />
                   </span>
                </h3>
            </div>
        </form>
    </body>
    </html>
    

Building a Custom Data Helper Class

The LOB system and data encapsulate the data layer logic of an application. You can create custom data helper classes to encapsulate the process of retrieving data from different LOB systems.

Connecting to LOB Data

Adventure Works Cycles stores LOB data in the AdventureWorks database. You can download and install the AdventureWorks sample database from the SQL Server 2005 Samples and Sample Databases (February 2007).

Next, you connect the ASP.NET Web site to the AdventureWorks database.

To connect the ASP.NET 2.0 Web site to the AdventureWorks database

  1. Open the web.config file.

  2. Add a connection string to the web.config file as follows:

    NoteNote:

    This code example includes line breaks to facilitate online viewing. You must remove the line breaks before running the code.

    Xml
    <connectionStrings>
       <add name="AWConnString" connectionString="Data Source=(local);
    Initial Catalog=AdventureWorks;Integrated Security=True" providerName=
    "System.Data.SqlClient"/>
    </connectionStrings>
    

Building a Custom Data Helper Class

Create a data helper class that retrieves sales information from the AdventureWorks database. The class provides two public methods:

  • GetSalesPersonData. To retrieve employee data such as FullName, Phone, Email, SalesQuota, SalesYTD, and TerritoryName. Use this data to populate the employee information on the generated document.

  • GetSalesByTerritory. To populate a table that shows all sales for 2003 and 2004 for all employees that belong to each employee's sales territory. This allows you to compare sales data and performance. Use this data to populate the sales territory table on the generated document.

To create a data helper class

  1. In Solution Explorer, right-click the Project folder, select Add New Item.

  2. In the Add New Item dialog box, select Class File.

  3. Name the file AdventureWorksSalesData.cs, and then click Add.

  4. In the Microsoft Visual Studio message box, select Yes to add the class file to the App_Code folder of your project.

  5. Replace the AdventureWorksSalesData.cs code with the following example code.

    C#
    using System;
    using System.Data;
    using System.Data.SqlClient;
    using System.Collections.Specialized;
    using System.Configuration;
    
  6. Add the following class, field, and constructor.

    C#
    /// <summary>
    /// Represents the AdventureWorks line of business sales data.
    /// </summary>
    public class AdventureWorksSalesData {
        private string _source;
    
        public AdventureWorksSalesData() {
            _source = ConfigurationManager.ConnectionStrings["AWConnString"].ConnectionString;
        }
    
  7. Create the GetSalesPersonData method.

    C#
        /// <summary>
        /// Get employee's data: FullName, Phone, Email,
        /// SalesQuota, SalesYTD, TerritoryName.
        /// </summary>
        /// <param name="salesPersonID">Data for employee with SalesPersonID</param>
        /// <returns>StringDictionary</returns>
        public StringDictionary GetSalesPersonData(string salesPersonID) {
            StringDictionary SalesPerson = new StringDictionary();
    
            //Connect to a Microsoft SQL Server database and get data
            const string query =
               "SELECT [FirstName] + ' ' + [LastName] AS [FullName],[Phone],
               [EmailAddress], [TerritoryName], [SalesQuota], [SalesYTD] FROM [AdventureWorks]
               .[Sales].[vSalesPerson] WHERE ([SalesPersonID] = @SalesPersonID)";
    
            using (SqlConnection conn = new SqlConnection(_source)) {
                conn.Open();
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.Parameters.AddWithValue("@SalesPersonID", salesPersonID);
                SqlDataReader dr = cmd.ExecuteReader();
    
                if (dr.Read()) {
    
                    SalesPerson.Add("FullName", (string)dr["FullName"]);
                    SalesPerson.Add("Phone", (string)dr["Phone"]);
                    SalesPerson.Add("Email", (string)dr["EmailAddress"]);
                    if (dr["SalesQuota"] is System.DBNull) {
                        SalesPerson.Add("SalesQuota", "0");
                    }
                    else {
                        SalesPerson.Add("SalesQuota", Convert.ToString((decimal)dr["SalesQuota"]));
                    }
                    if (dr["SalesYTD"] is System.DBNull) {
                        SalesPerson.Add("SalesYTD", "0");
                    }
                    else {
                        SalesPerson.Add("SalesYTD", Convert.ToString((decimal)dr["SalesYTD"]));
                    }
                    if (dr["TerritoryName"] is System.DBNull) {
                        SalesPerson.Add("TerritoryName", "NA");
                    }
                    else {
                        SalesPerson.Add("TerritoryName", (string)dr["TerritoryName"]);
                    }
                }
    
                dr.Close();
                conn.Close();
            }
    
            return SalesPerson;
        }
    
  8. Create the GetSalesByTerritory method.

    C#
        /// <summary>
        /// Get a table that shows all sales for 2003 and 2004 
        /// for all employees that belong to employee's
        /// sales territory so you can compare sales data
        /// and performance.
        /// </summary>
        /// <param name="Territory">Get all sales/employee for territory</param>
        /// <returns>Table with 2003 and 2004 sales/employee</returns>
        public DataTable GetSalesByTerritory(string Territory) {
            //Connect to a Microsoft SQL Server database and get data
            String source = ConfigurationManager.ConnectionStrings["AWConnString"].
            ConnectionString;
            string query = "SELECT [FullName], [2003], [2004] FROM [AdventureWorks].
            [Sales].[vSalesPersonSalesByFiscalYears] WHERE ([SalesTerritory] =
             '" + Territory + "')";
    
            using (SqlConnection conn = new SqlConnection(_source)) {
                conn.Open();
                SqlDataAdapter da = new SqlDataAdapter(query, conn);
                DataSet ds = new DataSet();
                da.Fill(ds, "SalesByTerritory");
                conn.Close();
    
                return ds.Tables["SalesByTerritory"];
            }
        }
    
  9. Close the class.

Building a Custom Data Helper Class to Generate Sales Report Summary Documents

The LOB software and components encapsulate the business logic tier of an application. You can create custom report-generation helper classes to encapsulate the process of document integration. These classes use the Open XML object model to generate document packages and output document content based on LOB data and business requirements.

Structure of the Report-Generation Helper Class

In this section, you create a report-generation helper class that retrieves sales information from the AdventureWorksSalesData class to create a report that looks like the one shown in Figure 2. The helper class allows you to group code for common formatting, style, and writing methods in the document.xml part.

This section is the core portion of the server-side document generation solution. You use the Open XML object model to create the document package and document parts. You use WordprocessingML markup to define the content of the document.

NoteNote:

Part 1 of this series, Building Server-Side Document Generation Solutions Using the Open XML Object Model (Part 1 of 2), explains the Open XML package architecture and WordprocessingML basic concepts used to build this report.

Figure 1 shows the tasks you define in the report-generation class. To elaborate on those steps:

  1. Create a document package.

    • Create a package as a Word 2007 document.

    • Insert a main document part: document.xml.

  2. Insert a style part and add it to the document package.

  3. Insert an image part and add it to the document package.

  4. Create the document.xml content by using WordprocessignML markup.

    • Insert an image within a paragraph.

    • Write a title within a paragraph.

    • Write employee’s contact information within a paragraph.

    • Write sales summary information within a paragraph.

    • Write the territory sales totals as a table.

Resource Files

This article shows how to use styles and images to a document by using the Open XML object model. Before you start implementing the report-generation helper class, you need to add resource files to work with images and styles.

Header Image Resource Files

All documents within Adventure Works Cycles are required to have a company logo in the document's header, as shown in Figure 3.

Figure 3. Adventure Works Cycle company logo
Adventure Works Cycle company logo

You use the Open XML object model to add an image part to the document package. To insert the header image, you need the following resources:

  • Image file

  • DrawingML template file

For more information about how to insert WordprocessingML markup to display pictures as part of the document content, see the "Image Part and Pictures" section in Building Server-Side Document Generation Solutions Using the Open XML Object Model (Part 1 of 2). You use the <w:drawing> element to embed a picture in a document. Instead of creating all the DrawingML markup, this sample solution uses a drawingTemplate.xml that writes all the <w:drawing> markup for you.

To add image resources to your project

  1. Right-click Solution Explorer, and then click New Folder.

  2. Name the new folder Resources.

  3. Right-click Figure 3 in this article and save the image to your hard disk.

  4. Rename the image to headerImage.gif.

  5. In Solution Explorer, right-click the Resources folder, select Add Existing Item, and then add the headerImage.gif image file.

  6. In Solution Explorer, right-click the Resources folder, and select Add New Item.

  7. In the Add New Item dialog box, select the XML File.

  8. Name the file drawingTemplate.xml, and then click Add.

  9. Replace the drawingTemplate.xml markup with the following sample markup.

    NoteNote:

    Line breaks are added to the following code example to facilitate online viewing. You must remove them before using this in your own code.

    Xml
    <!-- report generator helper class code will replace the {0} with the reference id of the image -->
    <container xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
    xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" 
    xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
      <w:drawing>
        <wp:inline>
          <wp:extent cx="1628936" cy="733586" />
          <wp:docPr name="image.gif" id="1" />
          <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
            <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
              <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
                <pic:nvPicPr>
                  <pic:cNvPr id="0" name="image.gif" />
                  <pic:cNvPicPr />
                </pic:nvPicPr>
                <pic:blipFill>
                  <a:blip r:embed="{0}" />
                  <a:stretch>
                    <a:fillRect />
                  </a:stretch>
                </pic:blipFill>
                <pic:spPr>
                  <a:xfrm>
                    <a:off x="0" y="0" />
                    <a:ext cx="1630190" cy="734151" />
                  </a:xfrm>
                  <a:prstGeom prst="rect" />
                </pic:spPr>
              </pic:pic>
            </a:graphicData>
          </a:graphic>
        </wp:inline>
      </w:drawing>
    </container>
    

The report-generation helper class uses the Open XML object model to:

  • Create an image part and add it to the document.

  • Get the reference ID for the image.

  • Replace the {0} of the drawingTemplate.xml file with the reference ID of the image.

  • Insert the DrawingML markup to the WordprocessingML markup in the main document part.

Styles Resource Files

You use the Open XML object model to insert a style part into the document package. You use WordprocessingML markup to define styles and formatting for paragraphs, text, runs, and tables.

To insert the style part, you need to modify the styles.xml file. For more information about how to create WordprocessingML markup to define styles, see the "Style part, styles, and formatting" section in Building Server-Side Document Generation Solutions Using the Open XML Object Model (Part 1 of 2). Use the styles.xml file to define a specific set of values for formatting properties that you can apply to document content. Instead of creating all the markup manually, this sample solution uses a styles.xml template.

To add the styles.xml resource file to your project

  1. In Solution Explorer, right-click the Resources folder, and select Add New Item

  2. In the Add New Item dialog box, select the XML file.

  3. Name the file styles.xml and click Add.

  4. Replace the styles.xml markup with the following sample markup.

    NoteNote:

    Line breaks are added to the following code example to facilitate online viewing. You must remove them before using this in your own code.

    Xml
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <w:styles xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" 
    xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
      <w:docDefaults>
        <w:rPrDefault>
          <w:rPr>
            <w:rFonts w:asciiTheme="minorHAnsi" w:eastAsiaTheme="minorEastAsia" w:hAnsiTheme="minorHAnsi" w:cstheme="minorBidi"/>
            <w:sz w:val="22"/>
            <w:szCs w:val="22"/>
            <w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA"/>
          </w:rPr>
        </w:rPrDefault>
        <w:pPrDefault>
          <w:pPr>
            <w:spacing w:after="200" w:line="276" w:lineRule="auto"/>
          </w:pPr>
        </w:pPrDefault>
      </w:docDefaults>
      <w:latentStyles w:defLockedState="0" w:defUIPriority="99" w:defSemiHidden="1"
     w:defUnhideWhenUsed="1" w:defQFormat="0" w:count="267">
        <w:lsdException w:name="Normal" w:semiHidden="0" w:uiPriority="0" w:unhideWhenUsed="0" w:qFormat="1"/>
        <w:lsdException w:name="heading 1" w:semiHidden="0" w:uiPriority="9" w:unhideWhenUsed="0" w:qFormat="1"/>
        <w:lsdException w:name="Title" w:semiHidden="0" w:uiPriority="10" w:unhideWhenUsed="0" w:qFormat="1"/>
        <w:lsdException w:name="Default Paragraph Font" w:uiPriority="1"/>
        <w:lsdException w:name="Subtitle" w:semiHidden="0" w:uiPriority="11" w:unhideWhenUsed="0" w:qFormat="1"/>
        <w:lsdException w:name="Strong" w:semiHidden="0" w:uiPriority="22" w:unhideWhenUsed="0" w:qFormat="1"/>
        <w:lsdException w:name="Table Grid" w:semiHidden="0" w:uiPriority="59" w:unhideWhenUsed="0"/>
        <w:lsdException w:name="Light List Accent 2" w:semiHidden="0" w:uiPriority="61" w:unhideWhenUsed="0"/>
      </w:latentStyles>
      <w:style w:type="paragraph" w:default="1" w:styleId="Normal">
        <w:name w:val="Normal"/>
        <w:qFormat/>
      </w:style>
      <w:style w:type="character" w:default="1" w:styleId="DefaultParagraphFont">
        <w:name w:val="Default Paragraph Font"/>
        <w:uiPriority w:val="1"/>
        <w:semiHidden/>
        <w:unhideWhenUsed/>
      </w:style>
      <w:style w:type="table" w:default="1" w:styleId="TableNormal">
        <w:name w:val="Normal Table"/>
        <w:uiPriority w:val="99"/>
        <w:semiHidden/>
        <w:unhideWhenUsed/>
        <w:qFormat/>
        <w:tblPr>
          <w:tblInd w:w="0" w:type="dxa"/>
          <w:tblCellMar>
            <w:top w:w="0" w:type="dxa"/>
            <w:left w:w="108" w:type="dxa"/>
            <w:bottom w:w="0" w:type="dxa"/>
            <w:right w:w="108" w:type="dxa"/>
          </w:tblCellMar>
        </w:tblPr>
      </w:style>
      <w:style w:type="table" w:styleId="LightList-Accent2">
        <w:name w:val="Light List Accent 2"/>
        <w:basedOn w:val="TableNormal"/>
        <w:uiPriority w:val="61"/>
        <w:rsid w:val="00693473"/>
        <w:pPr>
          <w:spacing w:after="0" w:line="240" w:lineRule="auto"/>
        </w:pPr>
        <w:tblPr>
          <w:tblStyleRowBandSize w:val="1"/>
          <w:tblStyleColBandSize w:val="1"/>
          <w:tblInd w:w="0" w:type="dxa"/>
          <w:tblBorders>
            <w:top w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
            <w:left w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
            <w:bottom w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
            <w:right w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
          </w:tblBorders>
          <w:tblCellMar>
            <w:top w:w="0" w:type="dxa"/>
            <w:left w:w="108" w:type="dxa"/>
            <w:bottom w:w="0" w:type="dxa"/>
            <w:right w:w="108" w:type="dxa"/>
          </w:tblCellMar>
        </w:tblPr>
        <w:tblStylePr w:type="firstRow">
          <w:pPr>
            <w:spacing w:before="0" w:after="0" w:line="240" w:lineRule="auto"/>
          </w:pPr>
          <w:rPr>
            <w:b/>
            <w:bCs/>
            <w:color w:val="FFFFFF" w:themeColor="background1"/>
          </w:rPr>
          <w:tblPr/>
          <w:tcPr>
            <w:shd w:val="clear" w:color="auto" w:fill="C0504D" w:themeFill="accent2"/>
          </w:tcPr>
        </w:tblStylePr>
        <w:tblStylePr w:type="lastRow">
          <w:pPr>
            <w:spacing w:before="0" w:after="0" w:line="240" w:lineRule="auto"/>
          </w:pPr>
          <w:rPr>
            <w:b/>
            <w:bCs/>
          </w:rPr>
          <w:tblPr/>
          <w:tcPr>
            <w:tcBorders>
              <w:top w:val="double" w:sz="6" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
              <w:left w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
              <w:bottom w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
              <w:right w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
            </w:tcBorders>
          </w:tcPr>
        </w:tblStylePr>
        <w:tblStylePr w:type="firstCol">
          <w:rPr>
            <w:b/>
            <w:bCs/>
          </w:rPr>
        </w:tblStylePr>
        <w:tblStylePr w:type="lastCol">
          <w:rPr>
            <w:b/>
            <w:bCs/>
          </w:rPr>
        </w:tblStylePr>
        <w:tblStylePr w:type="band1Vert">
          <w:tblPr/>
          <w:tcPr>
            <w:tcBorders>
              <w:top w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
              <w:left w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
              <w:bottom w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
              <w:right w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
            </w:tcBorders>
          </w:tcPr>
        </w:tblStylePr>
        <w:tblStylePr w:type="band1Horz">
          <w:tblPr/>
          <w:tcPr>
            <w:tcBorders>
              <w:top w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
              <w:left w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
              <w:bottom w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
              <w:right w:val="single" w:sz="8" w:space="0" w:color="C0504D" w:themeColor="accent2"/>
            </w:tcBorders>
          </w:tcPr>
        </w:tblStylePr>
      </w:style>
    </w:styles>
    

The report-generation helper class uses the Open XML object model to create a style part and add it to the document. Some WordprocessingML elements such as tables, paragraphs, and runs use the styles defined in the previous styles.xml document part.

Building a Custom Report-Generation Helper Class

Next, you create a report-generation helper class that includes all the business logic to create the sales summary report requested by management.

The report-generation helper class saves the reports to a Reports folder in the solution. This way you can also help provide a safe download hyperlink to the document.

To create a Reports folder

  1. Right-click Solution Explorer, and then click New Folder.

  2. Name the new folder Reports.

To generate the document content, you use System.Xml.XmlWriter to create the WordprocessingML markup. The amount of markup code you need to create depends on the complexity of the document content you are creating. As with all markup languages, Wordprocessingml is verbose.

To create a custom report-generation helper class

  1. In Solution Explorer, right-click the Project node, select Add New Item.

  2. In the Add New Item dialog box, select the Class File.

  3. Name the file SalesReportBuilder.cs, and then click Add.

  4. In the Microsoft Visual Studio message box, select Yes to add the Class File to the App_Code folder of your project.

  5. Replace the SalesReportBuilder.cs code with the sample code described in the following steps. Start by adding the following using statements.

    C#
    using System;
    using System.Data;
    using System.Collections.Specialized;
    using System.Configuration;
    using System.IO;
    using System.IO.Packaging;
    using System.Text;
    using System.Xml;
    using System.Web;
    using Microsoft.Office.DocumentFormat.OpenXml.Packaging;
    
  6. Add the following namespace, constants, fields, and constructor.

    C#
    /// <summary>
    /// Represents the complete sales report document to be generated.
    /// </summary>
    public class SalesReportBuilder {
        const string drawingTemplate = @"~/resources/drawingTemplate.xml";
        const string headerImageFile = @"~/resources/headerimage.gif";
        const string stylesXmlFile = @"~/resources/styles.xml";
        const string wordNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
        const string wordPrefix = "w";
    
        private string _documentName;
        private string _salesPersonID;
        private string _imagePartRID;
    
        public SalesReportBuilder(string salesPersonID) {
            _salesPersonID = salesPersonID;
            //Generate unique filename
            _documentName = HttpContext.Current.Server.MapPath(
            @"~/reports/AdventureWorks" + DateTime.Now.ToFileTime() + ".docx");
        }
    
  7. Create the region package and parts. This region has methods used to create the document package and parts.

    NoteNote:

    Line breaks are added to the following code example to facilitate online viewing. You must remove them before using this in your own code.

    C#
        #region Create Package and Parts
        /// <summary>
        /// 1. Create a new package as a Word document.
        /// 2. Add a style.xml part.
        /// 3. Add an embedded image part.
        /// 4. Create the document.xml part content. 
        /// </summary>
        /// <returns>File path location or error message</returns>
        public string CreateDocument() {
            try {
                using (WordprocessingDocument wordDoc = WordprocessingDocument.Create
                (_documentName, WordprocessingDocumentType.Document)) {
                    
                    // Set the content of the document so that Word can open it.
                    MainDocumentPart mainPart = wordDoc.AddMainDocumentPart();
    
                    // Create a style part and add it to the document.
                    XmlDocument stylesXml = new XmlDocument();
                    stylesXml.Load(HttpContext.Current.Server.MapPath(stylesXmlFile));
    
                    StyleDefinitionsPart stylePart = mainPart.AddNewPart<StyleDefinitionsPart>();
                    //  Copy the style.xml content into the new part....
                    using (Stream outputStream = stylePart.GetStream()) {
                        using (StreamWriter ts = new StreamWriter(outputStream)) {
                            ts.Write(stylesXml.InnerXml);
                        }
                    }
                    
                    // Create an image part and add it to the document.
                    ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Gif);
                    string imageFileName = System.Web.HttpContext.Current.Server.MapPath(headerImageFile);
                    using (FileStream stream = new FileStream(imageFileName, FileMode.Open)) {
                        imagePart.FeedData(stream);
                    }
                    
                    // Get the reference ID for the image added to the package.
                    // You will use the image part reference ID to insert the
                    // image to the document.xml file.
                    _imagePartRID = mainPart.GetIdOfPart(imagePart);
                    
                    // Create document.xml content.
                    SetMainDocumentContent(mainPart);
                }
    
                return (_documentName);
            }
            catch (Exception ex) {
                return (ex.Message);
    
            }
        }
    
        /// <summary>
        /// Set content of MainDocumentPart. 
        /// </summary>
        /// <param name="part">MainDocumentPart</param>
        public void SetMainDocumentContent(MainDocumentPart part) {
            using (Stream stream = part.GetStream()) {
                CreateWordProcessingML(stream);
            }
        }
        
       /// <summary>
        /// Generate WordprocessingML for Sales Report.
        /// The resulting XML will be saved as document.xml.
        /// </summary>
        /// <param name="stream">MainDocumentPart stream</param>
        public void CreateWordProcessingML(Stream stream) {
    
            // Get sales person data from AdventureWorks database
            // You will write this data to the document.xml file.
            AdventureWorksSalesData salesData = new AdventureWorksSalesData();
            StringDictionary SalesPerson = salesData.GetSalesPersonData(_salesPersonID);
    
            // Create an XmlWriter using UTF8 encoding.
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = Encoding.UTF8;
            settings.Indent = true;
    
            // This file represents the WordprocessingML of the Sales Report.
            XmlWriter writer = XmlWriter.Create(stream, settings);
            try {
                writer.WriteStartDocument(true);
                writer.WriteComment("This file represents the WordProcessingML of our Sales Report");
                writer.WriteStartElement(wordPrefix, "document", wordNamespace);
                writer.WriteStartElement(wordPrefix, "body", wordNamespace);
    
                WriteHeaderImage(writer);
                WriteDocumentTitle(writer, SalesPerson["FullName"]);
                WriteDocumentContactInfo(writer, SalesPerson["FullName"], SalesPerson["Phone"], SalesPerson["Email"]);
                WriteSalesSummaryInfo(writer, SalesPerson["SalesYTD"], SalesPerson["SalesQuota"]);
                WriteTerritoriesTable(writer, SalesPerson["TerritoryName"]);
    
                writer.WriteEndElement(); //body
                writer.WriteEndElement(); //document
            }
            catch (Exception e) {
                throw;
            }
            finally {
                //Write the XML to file and close the writer.
                writer.Flush();
                writer.Close();
            }
            return;
        }
        #endregion
    
  8. Create the region formatting methods. This region has methods used to create WordprocessingML used to format document content.

    C#
        #region Formatting Methods
        /// <summary>
        /// Write the title paragraph properties to the WordprocessingML document.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the properties to.</param>
        private void WriteTitleParagraphProperties(XmlWriter writer) {
            // Create the paragraph properties element.
            // </w:pPr>
            writer.WriteStartElement(wordPrefix, "pPr",
               wordNamespace);
    
            // Create the bottom border.
            //   <w:pBdr>
            //     <w:bottom w:val=”single” w:sz=”4” 
            //               w:space=”1” w:color=”auto” />
            //   </w:pBdr>
            writer.WriteStartElement(wordPrefix, "pBdr", wordNamespace);
            writer.WriteStartElement(wordPrefix, "bottom", wordNamespace);
            writer.WriteAttributeString(wordPrefix, "val", wordNamespace, "single");
            writer.WriteAttributeString(wordPrefix, "sz", wordNamespace, "4");
            writer.WriteAttributeString(wordPrefix, "space", wordNamespace, "1");
            writer.WriteAttributeString(wordPrefix, "color", wordNamespace, "blue");
            writer.WriteEndElement();
            writer.WriteEndElement();
    
            // Define the spacing for the paragraph.
            //   <w:spacing w:line=”240” w:lineRule=”auto” />
            writer.WriteStartElement(wordPrefix, "spacing", wordNamespace);
            writer.WriteAttributeString(wordPrefix, "line", wordNamespace, "240");
            writer.WriteAttributeString(wordPrefix, "lineRule", wordNamespace, "auto");
            writer.WriteEndElement();
    
            // Close the properties element.
            // </w:pPr>
            writer.WriteEndElement();
        }
    
        /// <summary>
        /// Write the title run properties to the WordprocessingML document.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the properties to.</param>
        private void WriteTitleRunProperties(XmlWriter writer) {
            // Create the run properties element.
            // <w:rPr>
            writer.WriteStartElement(wordPrefix, "rPr", wordNamespace);
    
            // Set up the spacing.
            //   <w:spacing w:val=”5” />
            writer.WriteStartElement(wordPrefix, "spacing", wordNamespace);
            writer.WriteAttributeString(wordPrefix, "val", wordNamespace, "5");
            writer.WriteEndElement();
    
            // Define the size.
            //   <w:sz w:val=”52” />
            writer.WriteStartElement(wordPrefix, "sz", wordNamespace);
            writer.WriteAttributeString(wordPrefix, "val", wordNamespace, "52");
            writer.WriteEndElement();
    
            // Close the properties element.
            // </w:rPr>
            writer.WriteEndElement();
        }
    
        /// <summary>
        /// Write the subtitle paragraph properties to the WordprocessingML document.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the properties to.</param>
        private void WriteSubtitleParagraphProperties(XmlWriter writer) {
            // Create the paragraph properties element.
            // <w:pPr>
            writer.WriteStartElement(wordPrefix, "pPr", wordNamespace);
    
            // Define the spacing for the paragraph.
            //   <w:spacing w:before=”200” w:after=”0” />
            writer.WriteStartElement(wordPrefix, "spacing", wordNamespace);
            writer.WriteAttributeString(wordPrefix, "before", wordNamespace, "200");
            writer.WriteAttributeString(wordPrefix, "after", wordNamespace, "0");
            writer.WriteEndElement();
    
            // Close the properties element.
            // </w:pPr>
            writer.WriteEndElement();
        }
    
        /// <summary>
        /// Write the subtitle run properties to the WordprocessingML document.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the properties to.</param>
        private void WriteSubtitleRunProperties(XmlWriter writer) {
            // Create the run properties element.
            // <w:rPr>
            writer.WriteStartElement(wordPrefix, "rPr", wordNamespace);
    
            // setup as bold
            //   <w:b />
            writer.WriteElementString(wordPrefix, "b", wordNamespace, null);
    
            // Define the size.
            //   <sz w:val=”26” />
            writer.WriteStartElement(wordPrefix, "sz", wordNamespace);
            writer.WriteAttributeString(wordPrefix,