ASP.NET Convert File to Office, ODF & PDF

2022-05-04

綜整 ASP.NET 開發,如何在後端處理檔案轉換 (Convert File),從 Microsoft Office 系列檔案 Docx, Xlsx 轉換為 ODT, ODS 以及 PDF 檔案。

使用的解決方案包含 LibreOffice SDK, Microsfot 以及開源或者是收費的第三方的元件。

logo

說明

關於多種文件格式、轉換的 .NET 解決方案

來源 目標 工具
Data .docx NPOI, DataTables, RDLC, SSRS
Data .xlsx NPOI, ClosedXML, DataTables, RDLC, SSRS
Data .pdf QuestPDF, iTextSharp, DataTables, RDLC, SSRS
Data .csv StringBuilder, DataTables, SSRS
.docx .odt LibreOffice SDK, Independentsoft, NDC ODF API
.docx .pdf LibreOffice SDK
.xlsx .ods LibreOffice SDK, Independentsoft, NDC ODF API
.xlsx .pdf LibreOffice SDK
.ppt .pdf LibreOffice SDK, Independentsoft, NDC ODF API
.ppt .pdf LibreOffice SDK

技術 技術成本
StringBuilder C# Feauters
NPOI 開源軟體
DataTables 開源軟體,前端資料處理
RDLC 微軟本機報表功能
SSRS 微軟報表伺服器,需要 SQL Server 授權以及部署伺服器
ClosedXML 開源軟體
LibreOffice SDK 開源軟體
Independentsoft 付費授權
NDC ODF API 開源軟體,需要部署伺服器
GemBox 付費授權

處理 ODF 檔案格式

說明如何使用 LibreOffice SDK 來處理轉檔為 ODF 以及 PDF 的方式。

soffice

作業系統安裝 LibreOffice 並使用 soffice.exe 進行轉檔

cd C:\Program Files (x86)\NDC ODF Application Tools 6\program

soffice --headless --convert-to odt "c:\tmp\temp.docx" --outdir "c:\tmp"
soffice --headless --convert-to pdf "c:\tmp\temp.docx" --outdir "c:\tmp"

Open Office Library

伺服器端需要安裝 LibreOffice 以及 LibreOffice SDK。

但藉由下列方式將 cli.dll 抽出,可以不需要安裝 LibreOffice SDK,參考威廉蕭在 Excel檔轉PDF產生的問題與解決方法 以及 Abdul Munim 在 HOW TO: Convert office documents to PDF using Open Office/LibreOffice in C# 所分享的方式。

將下列的 dll 複製到 VS 專案的 Library 資料夾,並且加入到 VS 專案的參考。

C:\Windows\Microsoft.NET\assembly\GAC_MSIL\cli_basetypes\v4.0_1.0.20.0__ce2cb7e279207b9e\cli_basetypes.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\cli_oootypes\v4.0_1.0.9.0__ce2cb7e279207b9e\cli_oootypes.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\cli_ure\v4.0_1.0.23.0__ce2cb7e279207b9e\cli_ure.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\cli_uretypes\v4.0_1.0.9.0__ce2cb7e279207b9e\cli_uretypes.dll
C:\Windows\Microsoft.NET\assembly\GAC_64\cli_cppuhelper\v4.0_1.0.23.0__ce2cb7e279207b9e\cli_cppuhelper.dll

如果只複製 LibreOffice SDK 但沒有安裝 LibreOffice 在伺服器端,會發生以下錯誤

未處理的例外狀況: System.Runtime.InteropServices.SEHException: 外部元件傳回例外狀況。
   於 cppu.bootstrap(Reference<com::sun::star::uno::XComponentContext>* )
   於 uno.util.Bootstrap.bootstrap()

Coding

專案會需要使用到下列的 Library。

using uno;
using uno.util;
using unoidl.com.sun.star.beans;
using unoidl.com.sun.star.frame;
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.uno;

Convert

public static bool Convert(string source, string target, string format, ref string error)
{
    error = string.Empty;
    if (!File.Exists(source))
    {
        error = "輸入的檔案不存在";
        return false;
    }
    if (GetFilterType(Path.GetExtension(source), format) == null)
    {
        error = "不正確的副檔名";
        return false;
    }
    FileInfo fileInfo = new FileInfo(source);
    fileInfo.IsReadOnly = false;

    XComponentContext context = Bootstrap.bootstrap();
    XMultiServiceFactory factory = (XMultiServiceFactory)context.getServiceManager();
    XComponentLoader aLoader =
        (XComponentLoader)factory.createInstance("com.sun.star.frame.Desktop");
    XComponent document = InitDocument(
        aLoader,
        string.Format("file:///{0}",
        source.Replace("\\", "/")),
        "_blank");

    int seconds = 30;
    while (document == null)
    {
        if (seconds <= 0)
        {
            error = $"LibreOffice 文件逾時 {seconds} 秒";
            return false;
        }
        Thread.Sleep(1000);
        seconds--;
    }

    target =
        ((target != null && target.Length != 0) ?
        string.Format("file:///{0}", target.Replace("\\", "/")) :
        string.Format("file:///{0}/{1}.{2}",
        Path.GetDirectoryName(source).Replace("\\", "/"),
        Path.GetFileNameWithoutExtension(source), format));

    SaveAsDocument(document, source, target, format);
    document.dispose();

    return true;
}

SaveAsDocument

private static void SaveAsDocument(
    XComponent xComponent, string sourceFile, string destinationFile, string format)
{
    PropertyValue[] array = (PropertyValue[])(object)new PropertyValue[2]
    {
        (PropertyValue)(object)new PropertyValue(),
        default(PropertyValue)
    };
    array[0].Name = "FilterName";
    array[0].Value = new Any(GetFilterType(Path.GetExtension(sourceFile), format));
    ((XStorable)xComponent).storeToURL(destinationFile, array);
    array[1] = (PropertyValue)(object)new PropertyValue();
    array[1].Name = "Overwrite";
    array[1].Value = new Any(true);
}

InitDocument

private static XComponent InitDocument(XComponentLoader aLoader, string file, string target)
{
    PropertyValue[] array = (PropertyValue[])(object)new PropertyValue[1]
    {
        (PropertyValue)(object)new PropertyValue()
    };
    array[0].Name = "Hidden";
    array[0].Value = new Any(true);
    return aLoader.loadComponentFromURL(file, target, 0, array);
}

GetFilterType

private static string GetFilterType(string extension, string format)
{
    switch (extension)
    {
        case ".doc":
        case ".docx":
        case ".txt":
        case ".rtf":
        case ".html":
        case ".htm":
        case ".xml":
        case ".odt":
        case ".wps":
        case ".wpd":
            switch (format)
            {
                case "pdf":
                    return "writer_pdf_Export";
                case "odf":
                    return "writer8";
                case "doc":
                    return "MS Word 97";
                case "html":
                    return "HTML (StarWriter)";
                default:
                    return null;
            }
        case ".xls":
        case ".xlsx":
        case ".ods":
            switch (format)
            {
                case "pdf":
                    return "calc_pdf_Export";
                case "odf":
                    return "calc8";
                case "html":
                    return "HTML (StarCalc)";
                default:
                    return null;
            }
        case ".ppt":
        case ".pptx":
        case ".odp":
            switch (format)
            {
                case "pdf":
                    return "impress_pdf_Export";
                case "odf":
                    return "draw8";
                case "html":
                    return "impress_html_Export";
                default:
                    return null;
            }
        default:
            return null;
    }
}

Program.cs

Convert(
    @"C:\tmp\temp.docx",
    @"c:\tmp\temp.odt", "odf",
    ref error
);

相關連結

ASP.NET MVC 從無到有打造一個應用系統

Visual Studio 入門教學