ASP.NET 案例分享:從 NPOI 匯出的 Excel 資料流建立附件並寄送

ASP.NET 案例分享:從 NPOI 匯出的 Excel 資料流建立附件並寄送

匯出 Excel 的需求屢見不鮮,做出這功能以目前的技術來說也已經不是難事,不過使用者總是能督促我們更上層樓,除了匯出 Excel,最好還能自動夾帶附件寄到指定的電子信箱。

匯出實體檔或不要?

開發這樣的功能其實不難,匯出 Excel 你會,寄送夾帶檔信件你也會,兩段串起來就是,所以問題在那?…問題在生成實體檔案!要知道 ASP.NET 是執行在 Server 端,產出 Excel 檔當然是先存放在 Server 端再進行下一步動作 (ex:提供下載 or 轉寄),這份檔案有時生命週期很短,大部分會在目的達成時立即清除以節省 Server 端硬碟空間,然而夾檔寄送電子郵件其實可以不用生成實體檔,好處是這樣就不存在後續刪除的問題。

構想

我的構想是這樣:
  1. 先用 NPOI 匯出 Excel 資料流 (stream)
  2. 接收資料流以初始化 Attachment 物件,附加到 MailMessage
  3. SmtpClient 類別發送郵件
這樣就能做到不產出實體檔寄送 Excel 附件

總的來說,實作的重點在步驟 2,畢竟以往大家較為習慣的方式是以實體檔夾帶附件,而忽略了 Sytem.Net.Mail.Attachment 類別其實是支援傳入資料流來建構新的執行個體,接下來我將實際演練一次供大家參考。

引用 NPOI

關於 NPOI 的應用,之前也曾經發過一篇文章:利用 NPOI Library 合併多個 Excel 檔,當時的版本是 1.2.1,經過這段時間 NPOI 發展到 1.2.3 beta,以本次需求來說新版本可以運作正常,惟與前一版相較之下,兩者寫法有些許不同,我會用最新版來實作,再補充 1.2.1 stable 版的 NPOI 寫法要如何修改。

想用最新版的人可自行上 http://npoi.codeplex.com/ 下載:

npoi_1.2.3_bin

下載回來的壓縮檔解開會得到 Ionic.Zip.dll、NPOI.dll 兩個組件,這一版開始 Ionic.Zip.dll 取代了以往的 ICSharpCode.SharpZipLib.dll,並且顯而易見地,NPOI 開發團隊似乎有意在這一版將整個 NPOI 專案編譯成單一 dll。

匯出 Excel 資料流

正式進入實作:
  1. 新增一個網站 (或 Web 應用程式專案),針對上面取得的 NPOI.dll 加入參考 (會一併引用 Ionic.Zip.dll)
    npoi_add_reference
  2. 為方便解說,直接開啟 Default.aspx.cs 撰寫程式,建立一個 DataTable 轉匯 Excel 資料流的方法
    private Stream RenderDataTableToExcel(DataTable srcTable)
    {
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = (HSSFSheet)workbook.CreateSheet();
        HSSFRow headerRow = (HSSFRow)sheet.CreateRow(0);
    
        // handling header.
        foreach (DataColumn column in srcTable.Columns)
            headerRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);
    
        // handling value.
        int rowIndex = 1;
    
        foreach (DataRow row in srcTable.Rows)
        {
            HSSFRow dataRow = (HSSFRow)sheet.CreateRow(rowIndex);
    
            foreach (DataColumn column in srcTable.Columns)
            {
                dataRow.CreateCell(column.Ordinal).SetCellValue(row[column].ToString());
            }
    
            rowIndex++;
        }
    
        MemoryStream stream = new MemoryStream();
        workbook.Write(stream);
        stream.Flush();
        stream.Position = 0;
    
        sheet = null;
        headerRow = null;
        workbook = null;
    
        return stream;
    }

    - or -

    使用 NPOI 1.2.1 stable 的朋友請這樣寫:
    private Stream RenderDataTableToExcel(DataTable srcTable)
    {
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.CreateSheet();
        HSSFRow headerRow = sheet.CreateRow(0);
    
        // handling header.
        foreach (DataColumn column in srcTable.Columns)
            headerRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);
    
        // handling value.
        int rowIndex = 1;
    
        foreach (DataRow row in srcTable.Rows)
        {
            HSSFRow dataRow = sheet.CreateRow(rowIndex);
    
            foreach (DataColumn column in srcTable.Columns)
            {
                dataRow.CreateCell(column.Ordinal).SetCellValue(row[column].ToString());
            }
    
            rowIndex++;
        }
    
        MemoryStream stream = new MemoryStream();
        workbook.Write(stream);
        stream.Flush();
        stream.Position = 0;
    
        sheet = null;
        headerRow = null;
        workbook = null;
    
        return stream;
    }

    造成差異的原因參考 NPOI 1.2.3 beta release notes 其中一段:
    npoi_1.2.3_beta_release_note

建構 Attachment

Attachment 的建構函式包含底下列表:

attachment_contructors

第一個參數若是 String 代表傳入實體檔路徑來建構附檔,這是大家最熟悉的方式,而傳入 Stream 則是本次所採用的方式,一樣可以初始化附檔,後續的參數不管是 String 或 ContentType 類型,都是用來指定附檔的內容類型 (Content-Type),其代表 MIME 協定所定義的電子郵件區段內容類型標頭,有興趣研究者可以參考文末連結,暫且簡單帶過,底下是實際撰寫的程式碼:

construct_attachment_with_xls_stream 

其餘的動作是使用 SmtpClient 類別經由 gmail 來發信,網路上的範例很多就不多說了,完整程式碼執行效果約略如圖:

send_mail_with_excel_attachment_expoted_from_npoi

範例可從這裡下載:SendAttachmentFromXlsStream.zip

結語

本案例出處是 Blueshop 的一則提問,我常覺得在技術論壇回答問題,是快速累積實戰經驗的絕佳途徑,因為個人能接觸到的問題有限,除非你有機會接觸大案子,否則從做中學得到的經驗恐怕不夠全面,透過網友的提問,你可以知道原來使用者提出的需求遠遠超出想像。此外,還能觀摩其他專家的解法,有時一個討論串下來不只獲得解題的成就感,還可以開拓視野,將自身的技術能力越練越純。


參考連結