MVP Records:

Windows Azure (2011)
ASP.NET (2007-2010)
Solution Architecture (2006)
SQL Server (2004-2005)

Award Records:

SQL Server 五虎將 (2011)
Hyper-V 金翅級戰士 (2012)

Get Microsoft Silverlight

我的著作:

1. Windows Azure 教戰手札(繁體版)
(點此進入書籍服務區)



走进云计算:Windows Azure实战手记 (簡體版)

2. ASP.NET 問題解決實戰
(點此進入書籍服務區)









 

 

 

技術資訊

線上書店

最新回應

前言:這個方法基本上是在想省錢,而且伺服器負載不大的情況才能做的,它本身有高度的風險,原因是 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:

image

會啟動 COM+ 的管理員,展開元件服務 > 電腦 > 我的電腦 > DCOM 設定,並找到 "Microsoft Word 97-2003 文件":

image

然後按右鍵,選 "內容",進入設定區:

image

選擇 "識別身份 (Identity)" 頁籤,並且選擇 "互動式使用者":

image

預設是 "執行啟動的使用者",這會讓 ASP.NET 的執行帳戶 (Network Service) 在呼叫物件模型時被拒絕,也就是若沒有設定這一項,會在程式執行時看到 "為具有 CLSID {000209FF-0000-0000-C000-000000000046} 的元件擷取 COM Class Factory 失敗: 80070005" 的錯誤訊息。

注意:如果是 IIS 6.0 的話,這個方法可能會失效,如果失效,請設定為 "使用下列使用者",並提供具有足夠權限的帳戶,或是直接使用 Administrator 帳戶,但實務上應極力避免使用 Administrator 帳戶。

除了這一項以外,在 "安全性" 頁籤中也要設定:

image

每一項均選自訂,並且按 "編輯" 進入編輯權限視窗:

image

將 ASP.NET 的執行帳戶加入,並且設定 "本機" 的部份允許即可。

存取權限部份設定也是相同:

image

設定權限也是相同 (亦可試試將完全控制取消,保留讀取權限):

image

這些工作完成後,我們就可以進入程式編寫的工作,程式本身其實並不困難,只要 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://stackoverflow.com/questions/607669/how-do-i-convert-word-files-to-pdf-programmatically

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

 


DotBlogs Tags: .NET DOC PDF Convert Word 2010 Office Object Model

關連文章

[Architecture] 系統職責的混搭不是流行,而是災難

[.NET] LINQ 的延遲執行 (Deferred Execution)

[.NET] Fluent Interface: 實作 Method Chaining 又不會有耦合性的作法

[碎碎念] 2011 年終盤點

回應

  • # re: [.NET][Office] 使用 Word 2010 在 Server 端將 DOC/DOCX 轉換成 PDF by Eric

    請問小朱,有推薦什麼在SERVER端轉PDF的元件,可以支援中文又可以穩定運作的元件嗎?如果暫不考慮價格的話..

    謝謝~~

    2012/1/12 下午 02:30 | 回覆

  • # re: [.NET][Office] 使用 Word 2010 在 Server 端將 DOC/DOCX 轉換成 PDF by 小朱

    上網用 "Convert doc to pdf" 就可以找到很多了。

    2012/1/12 下午 02:34 | 回覆

  • # re: [.NET][Office] 使用 Word 2010 在 Server 端將 DOC/DOCX 轉換成 PDF by Eric

    to 小朱 :
    是有很多..但是大部份都是中文有問題.

    所以想請教小朱有沒有相關的經驗

    謝謝小朱的回覆~~

    2012/1/12 下午 04:16 | 回覆

  • # re: [.NET][Office] 使用 Word 2010 在 Server 端將 DOC/DOCX 轉換成 PDF by 小朱

    我太窮了,買不起元件...所以沒有試用的經驗。

    2012/1/12 下午 04:20 | 回覆

  • # re: [.NET][Office] 使用 Word 2010 在 Server 端將 DOC/DOCX 轉換成 PDF by demo

    如果不是內部網站的話,我倒是做過整合 Google docs 的API 把檔案傳上去叫 Google 轉給我....只是這會扯到一些資安、網路流量的問題,如果案子沒有這方面的問題倒是可以考慮用這招XD

    2012/1/13 下午 02:42 | 回覆

  • # re: [.NET][Office] 使用 Word 2010 在 Server 端將 DOC/DOCX 轉換成 PDF by 亂馬客

     

    請教一下,
    1.如果用 互動式使用者 表示使用者是登入Server的那個帳號(要Console登入),為何還要設定network service權限呢?
     
    2.如果設定指定的使用者應該也是OK吧! 不過,如果有自造字的話,就一定要用 互動式使用者 了! 

    2012/1/18 上午 10:38 | 回覆

  • # re: [.NET][Office] 使用 Word 2010 在 Server 端將 DOC/DOCX 轉換成 PDF by 小朱

    to 亂馬客 :

    這篇文章是針對 ASP.NET,如果是一般的 Desktop 或 Console 應用,基本上是以使用者當下的帳戶來執行,就沒有這問題,但 ASP.NET 是以 Network Service (或其他指定來執行 ASP.NET 的帳戶) 跑的。

     

    2012/1/18 下午 12:11 | 回覆

  • # re: [.NET][Office] 使用 Word 2010 在 Server 端將 DOC/DOCX 轉換成 PDF by Darren

    我想請問一下

    這樣子做的話,是不是一定都要保持一個使用者是登入的狀態呢

    2012/4/19 下午 12:00 | 回覆

  • # re: [.NET][Office] 使用 Word 2010 在 Server 端將 DOC/DOCX 轉換成 PDF by 小朱

    to Darren :

    應該是不需要,只要有設 identity 的話,在啟動時就會自己設定好 identity。
     

    2012/4/19 下午 12:02 | 回覆

登入後使用進階評論

Please add 2 and 8 and type the answer here: