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

  • Overview

  • Business Scenario: Generating Server-Side Sales Report Summary Documents

  • Building a Server-Side Document Generation Solution

  • Conclusion

  • Additional Resources

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:

  • Microsoft .NET Framework 3.0. You need to use the System.IO.Packaging namespace. You can download and install the .NET Framework 3.0 from the .NET Framework Developer Center.

  • Open XML object model API. You can download and install the Open XML Format SDK 1.0 from the Microsoft Office Developer Center.

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.

<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.

    <%@ 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 xmlns="http://www.w3.org/1999/xhtml">
    <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.

    <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.

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

    /// <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.

        /// <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.

        /// <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.

    <!-- 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 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.

    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.

    /// <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.

        #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.

        #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, "val", wordNamespace, "26");
            writer.WriteEndElement();
    
            // Close the properties element.
            // </w:rPr>
            writer.WriteEndElement();
        }
    
        /// <summary>
        /// Write the bold run property to the WordprocessingML document.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the properties to.</param>
        private void WriteBoldRunProperties(XmlWriter writer) {
            // Create the run properties element.
            // <w:rPr>
            //   <w:b />
            // </w:rPr>
            writer.WriteStartElement(wordPrefix, "rPr", wordNamespace);
            writer.WriteElementString(wordPrefix, "b", wordNamespace, null);
            writer.WriteEndElement();
        }
        #endregion
    
  9. Create the region styles methods. This region has methods used to define style properties.

        region Styles Methods
        /// <summary>
        /// Write the style property to the WordprocessingML paragraph element.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the style to.</param>
        public void ApplyParagraphStyle(XmlWriter writer, string styleId) {
            // Apply the style in the paragraph properties.
            // <w:pPr>
            //   <w:pStyle w:val=”MyTitle” />
            // </w:pPr>
            writer.WriteStartElement(wordPrefix, "pPr", wordNamespace);
            writer.WriteStartElement(wordPrefix, "pStyle", wordNamespace);
            writer.WriteAttributeString(wordPrefix, "val", wordNamespace, styleId);
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
    
        /// <summary>
        /// Write the style property to the WordprocessingML table element.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the style to.</param>
        public void ApplyTableStyle(XmlWriter writer, string styleId) {
            // Apply the style in the table properties.
            // <w:tblPr>
            //   <w:tblStyle w:val="MyTableStyle" />
            //   <w:tblW w:w="0" w:type="auto" /> 
            //   <w:tblLook w:val="04A0" /> 
            // </w:tblPr>
            writer.WriteStartElement(wordPrefix, "tblPr", wordNamespace);
            writer.WriteStartElement(wordPrefix, "tblStyle", wordNamespace);
            writer.WriteAttributeString(wordPrefix, "val", wordNamespace, styleId);
            writer.WriteEndElement();
            writer.WriteStartElement(wordPrefix, "tblW", wordNamespace);
            writer.WriteAttributeString(wordPrefix, "w", wordNamespace, "0");
            writer.WriteAttributeString(wordPrefix, "type", wordNamespace, "auto");
            writer.WriteEndElement();
            writer.WriteStartElement(wordPrefix, "tblLook", wordNamespace);
            writer.WriteAttributeString(wordPrefix, "val", wordNamespace, "04A0");
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
        #endregion
    
  10. Create the region document.xml Writing Methods. This region has methods used to generate WordprocessingML markup used to define the content of the image, title, contact information, sales summary, and sales territory table sections of the document.

        region document.xml writing methods
        /// <summary>
        /// Writes an image within a paragraph
        /// into the WordprocessingML.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the image to.</param>
        private void WriteHeaderImage(XmlWriter writer) {
            // Load the drawing template into an XML document.
            XmlDocument drawingXml = new XmlDocument();
            string drawingXmlFile = System.Web.HttpContext.Current.Server.MapPath(drawingTemplate);
            drawingXml.Load(drawingXmlFile);
    
            // Load the drawing template into an XML document and pass the reference ID parameter.
            drawingXml.LoadXml(string.Format(drawingXml.InnerXml, _imagePartRID));
    
            // Write the wrapping paragraph and the drawing fragment.
            writer.WriteStartElement(wordPrefix, "p", wordNamespace);
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            drawingXml.DocumentElement.WriteContentTo(writer);
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
    
        /// <summary>
        /// Writes a document title within a paragraph
        /// into the WordprocessingML.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the image to.</param>
        /// <param name="title">Document title</param>
        private void WriteDocumentTitle(XmlWriter writer, string title) {
            // Create the title.
            // <w:p>
            //   <w:r>
            //     <w:t>Sales Report - Employee Name</w:t>
            //   </w:r>
            // </w:p>    
    
            writer.WriteStartElement(wordPrefix, "p", wordNamespace);
            WriteTitleParagraphProperties(writer);
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            WriteTitleRunProperties(writer);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, "Sales Report - " + title);
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
    
        /// <summary>
        /// Writes a document's contact information within a paragraph
        /// into the WordprocessingML.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the image to.</param>
        /// <param name="fullname">Employee's fullname</param>
        /// <param name="phone">Employee's phone number</param>
        /// <param name="email">Employee's e-mail address</param>
        private void WriteDocumentContactInfo(XmlWriter writer, string fullname, string phone, string email) {
    
            // Create the contact information section.
            // <w:p>
            //   <w:r>
            //     <w:t>Contact</w:t>
            //   </w:r>
            // </w:p>
    
            writer.WriteStartElement(wordPrefix, "p", wordNamespace);
            WriteSubtitleParagraphProperties(writer);
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            WriteSubtitleRunProperties(writer);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, "Contact");
            writer.WriteEndElement();
            writer.WriteEndElement();
    
            // Fill in the contact information section.
            // <w:p>
            //   <w:r>
            //     <w:t>Employee's fullname</w:t>
            //     <w:br />
            //     <w:t>sEmployee's e-mail</w:t>
            //     <w:br />
            //     <w:t>Employee's phone</w:t>
            //   </w:r>
            // </w:p>
            writer.WriteStartElement(wordPrefix, "p", wordNamespace);
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, fullname);
            writer.WriteElementString(wordPrefix, "br", wordNamespace, null);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, email);
            writer.WriteElementString(wordPrefix, "br", wordNamespace, null);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, phone);
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
    
        /// <summary>
        /// Writes the sales summary information within a paragraph
        /// into the WordprocessingML document.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the page break to.</param>
        /// <param name="totalsales">Employee's sales YTD</param>
        /// <param name="salesquota">Employee's sales quota</param>
        private void WriteSalesSummaryInfo(XmlWriter writer, string totalsales, string salesquota) {
            string wordNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
            string wordPrefix = "w";
    
            // Create the sales summary section.
            // <w:p>
            //   <w:r>
            //     <w:t>Sales Summary</w:t>
            //   </w:r>
            // </w:p>
            writer.WriteStartElement(wordPrefix, "p", wordNamespace);
            WriteSubtitleParagraphProperties(writer);
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            WriteSubtitleRunProperties(writer);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, "Sales Summary");
            writer.WriteEndElement();
            writer.WriteEndElement();
    
            // Fill in the contact information section.
            // <w:p>
            //   <w:r>
            //     <w:t>Total Sales:</w:t>
            //   </w:r>
            //   <w:r>
            //     <w:t>Employee's sales YTD</w:t>
            //   </w:r>
            //   <w:br />
            //   <w:r>
            //     <w:t>Employee's sales quota</w:t>
            //   </w:r>
            //   <w:r>
            //     <w:t>$1000.00</w:t>
            //   </w:r>
            // </w:p>
            writer.WriteStartElement(wordPrefix, "p", wordNamespace);
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, "Total Sales:");
            writer.WriteElementString(wordPrefix, "tab", wordNamespace, null);
            writer.WriteEndElement();
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, totalsales);
            writer.WriteEndElement();
            writer.WriteElementString(wordPrefix, "br", wordNamespace, null);
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, "Sales Quota:");
            writer.WriteElementString(wordPrefix, "tab", wordNamespace, null);
            writer.WriteEndElement();
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, salesquota);
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
    
        /// <summary>
        /// Writes the territory sales totals as a table to the WordprocessingML document.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the page break to.</param>
        private void WriteTerritoriesTable(XmlWriter writer, string territory) {
            string wordNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
            string wordPrefix = "w";
    
            // Create the territory section header.
            // <w:p>
            //   <w:r>
            //     <w:t>Sales by Territory</w:t>
            //   </w:r>
            // </w:p>
            writer.WriteStartElement(wordPrefix, "p", wordNamespace);
            WriteSubtitleParagraphProperties(writer);
            ApplyParagraphStyle(writer, "Heading 3");
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            WriteSubtitleRunProperties(writer);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, "Sales by Territory - " + territory);
            writer.WriteEndElement();
            writer.WriteEndElement();
    
            // Open the table element.
            writer.WriteStartElement(wordPrefix, "tbl",
               wordNamespace);
            ApplyTableStyle(writer, "LightList-Accent2");
    
            // Write table headings.
            writer.WriteStartElement(wordPrefix, "tr", wordNamespace);
            writer.WriteStartElement(wordPrefix, "tc", wordNamespace);
            writer.WriteStartElement(wordPrefix, "p", wordNamespace);
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, "Employee");
            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteEndElement();
    
            writer.WriteStartElement(wordPrefix, "tc", wordNamespace);
            writer.WriteStartElement(wordPrefix, "p", wordNamespace);
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, "2003");
            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteEndElement();
    
            writer.WriteStartElement(wordPrefix, "tc", wordNamespace);
            writer.WriteStartElement(wordPrefix, "p", wordNamespace);
            writer.WriteStartElement(wordPrefix, "r", wordNamespace);
            writer.WriteElementString(wordPrefix, "t", wordNamespace, "2004");
            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteEndElement();
    
            // write the close row
            writer.WriteEndElement();
    
            // write a row for each territory
            AdventureWorksSalesData salesData = new AdventureWorksSalesData();
            DataTable dt = salesData.GetSalesByTerritory(territory);
    
            foreach (DataRow myRow in dt.Rows) {
                writer.WriteStartElement(wordPrefix, "tr", wordNamespace);
    
                foreach (DataColumn myCol in dt.Columns) {
                    writer.WriteStartElement(wordPrefix, "tc", wordNamespace);
                    writer.WriteStartElement(wordPrefix, "p", wordNamespace);
                    writer.WriteStartElement(wordPrefix, "r", wordNamespace);
                    writer.WriteElementString(wordPrefix, "t", wordNamespace, myRow[myCol].ToString());
                    writer.WriteEndElement();
                    writer.WriteEndElement();
                    writer.WriteEndElement();
    
                }
                // Write the close row.
                writer.WriteEndElement();
            }
            // end the table element
            writer.WriteEndElement();
        }
    
        #endregion
    
    }
    
  11. Close the class.

Running the Application

You are now ready to run the application to select an employee and export sales summary reports to Word 2007. The application saves the generated reports to your local hard disk.

To run the application

  1. Press F5 to build and run the Web application.

  2. Select an employee from the DropDownList control.

  3. Click the Export to Word 2007 button.

    The application displays the file path of the generated document.

  4. Open the document to see the resulting file.

After the server-side solution creates the documents, you can open the reports by using 2007 Microsoft Office programs to view or manipulate the documents.

Users working with earlier versions of Microsoft Office can open, modify, and save the generated documents by downloading and installing the Microsoft Office Compatibility Pack for Word, Excel, and PowerPoint 2007. This download allows you to share Word 2007, Excel 2007, and PowerPoint 2007 documents with Office 2000, Office XP, or Office 2003 users.

Conclusion

The Open XML Formats unlock new possibilities for server-side solutions. You can create and manipulate documents without installing the Microsoft Office applications on the server. The formats also help organizations to provide reports that expose LOB data.

The Open XML object model simplifies the process of programmatically generating and manipulating Open XML document packages and document parts in applications based on the .NET Framework.

This document shows how to build a server-side Word 2007 document-generation solution by using the Open XML object model and ASP.NET 2.0. In the same way, you can use the Open XML object model and SpreadsheetML to export reports to Excel and PresentationML to automate the process of generating PowerPoint presentations.

Acknowledgments

I want to thank Doug Mahugh, Wouter van Vugt, and Frank Rice for their contributions to this article.

Additional Resources

For additional information, see the following resources: