Blog of a Filipino Developer about C#, VB.NET, ASP.NET, Java, PHP, SQL Server, MySql and Oracle RSS 2.0
 Saturday, June 09, 2007

A few weeks ago I wrote an article about merging word documents in C# and got great response about the article. One of the readers, Abhi, had an interesting problem. The application he wrote was throwing this error:

Word was unable to read this document. It may be corrupt. Try one or more of the following: * Open and Repair the file. * Open the file with the Text Recovery converter. 

Upon further inspection, I realized that the issue was being raised by this line:

// Create a new file based on our template
Word._Document wordDocument = wordApplication.Documents.Add(
                                   ref defaultTemplate
                                 , ref missing
                                 , ref missing
                                 , ref missing);

What that line does is it tries to locate the default Microsoft Word template(Normal.dot) and use it as the base template for the new Word document being created. I did a Filemon on the application and found that I wasn't able to locate the Normal.dot file when the class is being used in a web application. The way to get around this problem is by assigning the path to the Normal.dot file inside the application via the web config. With that being said, I modified my first class and implemented the solution.

In your web.config, please add this key inside your appSettings section:

<appSettings>

<add key="KeithRull.Utilities.OfficeInterop.DefaultWordTemplate" value="-change this to your template location(e.g c:\normal.dot)-"/>

</appSettings>

Next, Update your MsWord.cs. I've added several bug fixes to it(including the nasty page break at the end of each document). Below is the C# version of our class

using System;
using Word = Microsoft.Office.Interop.Word;
using System.Configuration;

namespace KeithRull.Utilities.OfficeInterop
{
    public class MsWord
    {
        /// <summary>
        /// This is the default Word Document Template file. I suggest that you point this to the location
        /// of your Ms Office Normal.dot file which is usually located in your Ms Office Templates folder.
        /// If it does not exist, what you could do is create an empty word document and save it as Normal.dot.
        /// </summary>
        private static string defaultWordDocumentTemplate = ConfigurationManager.AppSettings["KeithRull.Utilities.OfficeInterop.DefaultWordTemplate"].ToString();

        /// <summary>
        /// A function that merges Microsoft Word Documents that uses the default template
        /// </summary>
        /// <param name="filesToMerge">An array of files that we want to merge</param>
        /// <param name="outputFilename">The filename of the merged document</param>
        /// <param name="insertPageBreaks">Set to true if you want to have page breaks inserted after each document</param>
        public static void Merge(string[] filesToMerge, string outputFilename, bool insertPageBreaks)
        {
            Merge(filesToMerge, outputFilename, insertPageBreaks, defaultWordDocumentTemplate);
        }

        /// <summary>
        /// A function that merges Microsoft Word Documents that uses a template specified by the user
        /// </summary>
        /// <param name="filesToMerge">An array of files that we want to merge</param>
        /// <param name="outputFilename">The filename of the merged document</param>
        /// <param name="insertPageBreaks">Set to true if you want to have page breaks inserted after each document</param>
        /// <param name="documentTemplate">The word document you want to use to serve as the template</param>
        public static void Merge(string[] filesToMerge, string outputFilename, bool insertPageBreaks, string documentTemplate)
        {
            object defaultTemplate = documentTemplate;
            object missing = System.Type.Missing;
            object pageBreak = Word.WdBreakType.wdPageBreak;
            object outputFile = outputFilename;

            // Create  a new Word application
            Word._Application wordApplication = new Word.Application();

            try
            {
                // Create a new file based on our template
                Word._Document wordDocument = wordApplication.Documents.Add(
                                              ref defaultTemplate
                                            , ref missing
                                            , ref missing
                                            , ref missing);

                // Make a Word selection object.
                Word.Selection selection = wordApplication.Selection;

                //Count the number of documents to insert;
                int documentCount = filesToMerge.Length;

                //A counter that signals that we shoudn't insert a page break at the end of document.
                int breakStop = 0;

                // Loop thru each of the Word documents
                foreach (string file in filesToMerge)
                {
                    breakStop++;
                    // Insert the files to our template
                    selection.InsertFile(
                                                file
                                            , ref missing
                                            , ref missing
                                            , ref missing
                                            , ref missing);

                    //Do we want page breaks added after each documents?
                    if (insertPageBreaks && breakStop != documentCount)
                    {
                        selection.InsertBreak(ref pageBreak);
                    }
                }

                // Save the document to it's output file.
                wordDocument.SaveAs(
                                ref outputFile
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing
                            , ref missing);

                // Clean up!
                wordDocument = null;
            }
            catch (Exception ex)
            {
                //I didn't include a default error handler so i'm just throwing the error
                throw ex;
            }
            finally
            {
                // Finally, Close our Word application
                wordApplication.Quit(ref missing, ref missing, ref missing);
            }
        }
    }
}

A VB.NET version of the class was also requested so I decided to add it to this article, a notable difference between the C# and the VB.NET version is that the VB.NET version doesn't have ref missing all over the place. This is because VB.NET support optional parameters and C# does not. Below is the VB.NET version:

Imports System
Imports Word = Microsoft.Office.Interop.Word
Imports System.Configuration

Namespace KeithRull.Utilities.OfficeInterop
    Public Class MsWord
        ''' <summary>
        ''' This is the default Word Document Template file. I suggest that you point this to the location
        ''' of your Ms Office Normal.dot file which is usually located in your Ms Office Templates folder.
        ''' If it does not exist, what you could do is create an empty word document and save it as Normal.dot.
        ''' </summary>
        Private Shared defaultWordDocumentTemplate As String = ConfigurationManager.AppSettings("KeithRull.Utilities.OfficeInterop.DefaultWordTemplate").ToString()

        ''' <summary>
        ''' A function that merges Microsoft Word Documents that uses the default template
        ''' </summary>
        ''' <param name="filesToMerge">An array of files that we want to merge</param>
        ''' <param name="outputFilename">The filename of the merged document</param>
        ''' <param name="insertPageBreaks">Set to true if you want to have page breaks inserted after each document</param>
        Public Shared Sub Merge(ByVal filesToMerge As String(), ByVal outputFilename As String, ByVal insertPageBreaks As Boolean)
            Merge(filesToMerge, outputFilename, insertPageBreaks, defaultWordDocumentTemplate)
        End Sub

        ''' <summary>
        ''' A function that merges Microsoft Word Documents that uses a template specified by the user
        ''' </summary>
        ''' <param name="filesToMerge">An array of files that we want to merge</param>
        ''' <param name="outputFilename">The filename of the merged document</param>
        ''' <param name="insertPageBreaks">Set to true if you want to have page breaks inserted after each document</param>
        ''' <param name="documentTemplate">The word document you want to use to serve as the template</param>
        Public Shared Sub Merge(ByVal filesToMerge As String(), ByVal outputFilename As String, ByVal insertPageBreaks As Boolean, ByVal documentTemplate As String)
            Dim defaultTemplate As Object = documentTemplate
            Dim pageBreak As Object = Word.WdBreakType.wdPageBreak
            Dim outputFile As Object = outputFilename

            ' Create a new Word application
            Dim wordApplication As Word._Application = New Word.Application()

            Try
                ' Create a new file based on our template
                Dim wordDocument As Word._Document = wordApplication.Documents.Add(defaultTemplate)

                ' Make a Word selection object.
                Dim selection As Word.Selection = wordApplication.Selection

                'Count the number of documents to insert;
                Dim documentCount As Integer = filesToMerge.Length

                'A counter that signals that we shoudn't insert a page break at the end of document.
                Dim breakStop As Integer = 0

                ' Loop thru each of the Word documents
                For Each file As String In filesToMerge
                    breakStop += 1
                    ' Insert the files to our template
                    selection.InsertFile(file)

                    'Do we want page breaks added after each documents?
                    If insertPageBreaks AndAlso breakStop <> documentCount Then
                        selection.InsertBreak(pageBreak)
                    End If
                Next

                ' Save the document to it's output file.
                wordDocument.SaveAs(outputFile)

                ' Clean up!
                wordDocument = Nothing
            Catch ex As Exception
                'I didn't include a default error handler so i'm just throwing the error
                Throw ex
            Finally
                ' Finally, Close our Word application
                wordApplication.Quit()
            End Try
        End Sub
    End Class
End Namespace

To use this class, all you need to do is add the necessary references(Microsoft.Office.Core & Microsoft.Office.Interop.Word) and include my MsWord class in your project. A sample method call is listed below.

protected void mergeDocumentsButton_Click(object sender, EventArgs e)
{
   try
   {
      string document1 = document1FileUpload.PostedFile.FileName;
      string document2 = document2FileUpload.PostedFile.FileName;

      string[] documentsToMerge = { document1, document2 };

      string outputFileName = String.Format("d:\\{0}.doc", Guid.NewGuid());

      MsWord.Merge(documentsToMerge, outputFileName, true);

      messageLabel.Text = outputFileName;
   }
   catch (Exception ex)
   {
      messageLabel.Text = ex.Message;
   }
}

You can download the sample project here including the VB.NET and C# class.

KeithRull.MergeWordDocuments.WebApp.zip (14.68 KB)

*Note: you might need to update the references of the project since I only have Office 2007 installed in my machine and the references that I used was the Office 2007 Primary Interop Assemblies.

Want to comment? Got a question regarding this article? Post it here!!!!!

Saturday, June 09, 2007 5:20:55 PM (GMT Standard Time, UTC+00:00)  #    Comments [0] -
.NET
Archive
<June 2007>
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2008
Keith Rull
Sign In
Statistics
Total Posts: 254
This Year: 51
This Month: 0
This Week: 0
Comments: 111
Themes
Pick a theme:
Ads
All Content © 2008, Keith Rull
DasBlog theme 'Business' created by Christoph De Baene (delarou)