[.NET][Office] 使用 Word 2010 在 Server 端将 DOC/DOCX 转换成 PDF

这个需求真的是老需求了,只有使客户端有 Office,就难免会有这种需求,像是在 server 上产生 Word, Excel 或是将表格转换成 Word/Excel 格式下载的,而这次碰到的需求是要将 Word 转换成 PDF,只是目前市场上可用的免费工具如 itextsharp, pdfFactory 这种,都不能支持由 server 转换文档为 PDF,而一些可转换的组件要钱而且很贵 ($599 镁以上,可转散布的更贵),在一个预算有限的项目上,仅能使用最原始的方式来实践这个功能,毕竟 $399 还是比 $599 便宜多了...


前言:这个方法基本上是在想省钱,而且服务器负载不大的情况才能做的,它本身有高度的风险,原因是 Office 2010 用户端程序虽有强大的对象模型,但它并不是针对服务器环境来设计,因此会有不少的副作用,故不能作为追求稳定的应用程序或服务的最佳解决方案,请参阅 Microsoft Support 上一篇文章:Office 服务器端自动化的考量因素

这个需求真的是老需求了,只有使客户端有 Office,就难免会有这种需求,像是在 server 上产生 Word, Excel 或是将表格转换成 Word/Excel 格式下载的,而这次碰到的需求是要将 Word 转换成 PDF,只是目前市场上可用的免费工具如 itextsharp, pdfFactory 这种,都不能支持由 server 转换文档为 PDF,而一些可转换的组件要钱而且很贵 ($599 镁以上,可转散布的更贵),在一个预算有限的项目上,仅能使用最原始的方式来实践这个功能,毕竟 $399 还是比 $599 便宜多了。

在编写程序之前,先来说明服务器的设定,为了要将 DOC/DOCX 转换成 PDF,在服务器上安装一套 Microsoft Word 2010 (或 Microsoft Office 2010) 是免不了的,如果是 Office 2007 的话,那可能还需要加装一套 Save To PDF 的扩充套件,至于 Word 2003 以前的版本,则真的不支持存成 PDF,所以不在本文讨论之列。

接着,为了能让服务器端程序 (本文以 ASP.NET 为例) 可存取对象模型,我们必须要在 Word 上设定 DCOM 的存取权限 (Word/Office 是 COM Automation Server),在 "执行" 中输入 dcomcnfg:

[.NET][Office] 使用 Word 2010 在 Server 端将 DOC/DOCX 转换成 PDF

会启动 COM+ 的管理员,展开组件服务 > 电脑 > 我的电脑 > DCOM 设定,并找到 "Microsoft Word 97-2003 文档":

[.NET][Office] 使用 Word 2010 在 Server 端将 DOC/DOCX 转换成 PDF

然后按右键,选 "内容",进入设定区:

[.NET][Office] 使用 Word 2010 在 Server 端将 DOC/DOCX 转换成 PDF

选择 "识别身份 (Identity)" 页签,并且选择 "交互式使用者":

[.NET][Office] 使用 Word 2010 在 Server 端将 DOC/DOCX 转换成 PDF

默认是 "执行启动的使用者",这会让 ASP.NET 的执行账户 (Network Service) 在调用对象模型时被拒绝,也就是若没有设定这一项,会在程序执行时看到 "为具有 CLSID {000209FF-0000-0000-C000-000000000046} 的组件撷取 COM Class Factory 失败: 80070005" 的错误消息。

注意:如果是 IIS 6.0 的话,这个方法可能会失效,如果失效,请设定为 "使用下列使用者",并提供具有足够权限的账户,或是直接使用 Administrator 账户,但实际上应极力避免使用 Administrator 账户。

除了这一项以外,在 "安全性" 页签中也要设定:

[.NET][Office] 使用 Word 2010 在 Server 端将 DOC/DOCX 转换成 PDF

每一项均选自订,并且按 "编辑" 进入编辑权限窗口:

[.NET][Office] 使用 Word 2010 在 Server 端将 DOC/DOCX 转换成 PDF

将 ASP.NET 的执行账户加入,并且设定 "本机" 的部分允许即可。

存取权限部分设定也是相同:

[.NET][Office] 使用 Word 2010 在 Server 端将 DOC/DOCX 转换成 PDF

设定权限也是相同 (亦可试试将完全控制取消,保留读取权限):

[.NET][Office] 使用 Word 2010 在 Server 端将 DOC/DOCX 转换成 PDF

这些工作完成后,我们就可以进入程序编写的工作,程序本身其实并不困难,只要 Google 一下 "C# Word PDF",就可以找到不少有用的参考数据,而我自己写的也给大家参考:


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Office;
using Microsoft.Office.Interop;
using Microsoft.Office.Interop.Word;

namespace OfficeToPDFConverter
{
    public enum WordFileFormat
    {
        WordDoc,
        WordDocx
    }

    public class WordConverter
    {
        public static byte[] Convert(byte[] DocFileData, WordFileFormat Format, string TempDirPath)
        {
            string docTempFileName = Guid.NewGuid().ToString();
            string pdfTempFileName = Guid.NewGuid().ToString() + ".pdf";

            if (Format == WordFileFormat.WordDoc)
                docTempFileName += ".doc";
            else if (Format == WordFileFormat.WordDocx)
                docTempFileName += ".docx";
            else
                throw new NotSupportedException("ERROR_DOC_FORMAT_NOT_SUPPORTED");

            object optionalNullParam = Type.Missing;
            MemoryStream stream = new MemoryStream();
            Application wordapp = WordAppInstance.GetInstance();
            object tempDocFilePath = TempDirPath + @"" + docTempFileName;
            object tempPdfFilePath = TempDirPath + @"" + pdfTempFileName;
            object wordDocSaveAs = WdSaveFormat.wdFormatPDF;
            object wordCloseOption = WdSaveOptions.wdDoNotSaveChanges;

            FileStream tempStream = new FileStream(tempDocFilePath.ToString(), FileMode.Create, FileAccess.Write);
            tempStream.Write(DocFileData, 0, DocFileData.Length);
            tempStream.Flush();
            tempStream.Close();

            Document docInstance = wordapp.Documents.Open(
                ref tempDocFilePath, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, 
                ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, 
                ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam);

            docInstance.Activate();
            docInstance.SaveAs(
                ref tempPdfFilePath, ref wordDocSaveAs, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, 
                ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, 
                ref optionalNullParam, ref optionalNullParam, ref optionalNullParam, ref optionalNullParam);

            ((_Document)docInstance).Close(ref wordCloseOption, ref optionalNullParam, ref optionalNullParam);
            docInstance = null;

            FileStream pdfStream = new FileStream(tempPdfFilePath.ToString(), FileMode.Open, FileAccess.Read);
            byte[] pdfData = new byte[pdfStream.Length];
            pdfStream.Read(pdfData, 0, pdfData.Length);
            pdfStream.Close();

            // delete temp file.
            File.Delete(docTempFileName);
            File.Delete(pdfTempFileName);

            return pdfData;
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office;
using Microsoft.Office.Interop;
using Microsoft.Office.Interop.Word;

namespace OfficeToPDFConverter
{
    public class WordAppInstance
    {
        private static Microsoft.Office.Interop.Word.Application _wordApplication = null;

        public static Microsoft.Office.Interop.Word.Application GetInstance()
        {
            if (_wordApplication == null)
            {
                _wordApplication = new Microsoft.Office.Interop.Word.ApplicationClass();
                _wordApplication.Visible = false;
                _wordApplication.ScreenUpdating = false;

                return _wordApplication;
            }
            else
            {
                return _wordApplication;
            }
        }

        public static void Free()
        {
            if (_wordApplication != null)
            {
                object optionalNullParam = Type.Missing;
                object wordSaveOption = WdSaveOptions.wdDoNotSaveChanges;

                ((_Application)_wordApplication).Quit(ref wordSaveOption, ref optionalNullParam, ref optionalNullParam);
                _wordApplication = null;

                GC.Collect();
            }
        }
    }
}

注意:项目中要加入 Microsoft.Office.Interop.Word (14.0) 的参考。

然后就可以在 ASP.NET 上写程序了:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebTesting
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void cmdConvert_Click(object sender, EventArgs e)
        {
            if (this.upWordDoc.HasFile)
            {
                byte[] d = null;

                if (this.upWordDoc.FileName.ToLower().EndsWith(".doc"))
                {
                    d = OfficeToPDFConverter.WordConverter.Convert(
                        this.upWordDoc.FileBytes, 
                        OfficeToPDFConverter.WordFileFormat.WordDoc, 
                        Server.MapPath(VirtualPathUtility.ToAbsolute("~/PdfBuffer")));
                }
                else if (this.upWordDoc.FileName.ToLower().EndsWith(".docx"))
                {
                    d = OfficeToPDFConverter.WordConverter.Convert(
                        this.upWordDoc.FileBytes,
                        OfficeToPDFConverter.WordFileFormat.WordDocx,
                        Server.MapPath(VirtualPathUtility.ToAbsolute("~/PdfBuffer")));
                }

                Response.AddHeader("Content-Disposition", "attachment; filename=Test.pdf");
                Response.ContentType = "application/octet-stream";
                Response.BinaryWrite(d);
                Response.End();
            }
        }
    }
}

注意:因为 Office 对象模型不接受 Stream,必须要另存文件才可用 Office 对象模型操作。

Reference:

http://support.microsoft.com/kb/257757/zh-tw

http://www.dotblogs.com.tw/nobel12/archive/2010/05/12/15170.aspx

http://*.com/questions/607669/how-do-i-convert-word-files-to-pdf-programmatically

http://www.codeproject.com/KB/cs/sertf2pdf.aspx

原文:大专栏  [.NET][Office] 使用 Word 2010 在 Server 端将 DOC/DOCX 转换成 PDF


上一篇:批量定时任务将rtf文件转为docx,入参是rtf文件夹,生成一个docx文件夹


下一篇:python处理word文档中run的详解