
"Combine. Merge. Join. Append. Concatenate. Microsoft Word Documents."
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.
Posted
06-09-2007 10:13 AM
by
keithrull