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 templateWord._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:
<
</
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 SystemImports Word = Microsoft.Office.Interop.WordImports System.ConfigurationNamespace 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 ClassEnd 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!!!!!
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.