[轉貼] 從 NPOI 匯出的 Excel 資料流建立附件並寄送

2012031217:05
匯出 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 資料流的方法      
    01 // using NPOI.HSSF.UserModel;
    02 private Stream RenderDataTableToExcel(DataTable srcTable)
    03 {
    04     HSSFWorkbook workbook = new HSSFWorkbook();
    05     HSSFSheet sheet = (HSSFSheet)workbook.CreateSheet();
    06     HSSFRow headerRow = (HSSFRow)sheet.CreateRow(0);
    07  
    08     // handling header.
    09     foreach (DataColumn column in srcTable.Columns)
    10         headerRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);
    11  
    12     // handling value.
    13     int rowIndex = 1;
    14  
    15     foreach (DataRow row in srcTable.Rows)
    16     {
    17         HSSFRow dataRow = (HSSFRow)sheet.CreateRow(rowIndex);
    18  
    19         foreach (DataColumn column in srcTable.Columns)
    20         {
    21            dataRow.CreateCell(column.Ordinal).SetCellValue(row[column].ToString());
    22         }
    23  
    24         rowIndex++;
    25     }
    26  
    27     MemoryStream stream = new MemoryStream();
    28     workbook.Write(stream);
    29     stream.Flush();
    30     stream.Position = 0;
    31  
    32     sheet = null;
    33     headerRow = null;
    34     workbook = null;
    35  
    36     return stream;
    37 }

    - or -     

    使用 NPOI 1.2.1 stable 的朋友請這樣寫:     
    01 // using NPOI.HSSF.UserModel;
    02 private Stream RenderDataTableToExcel(DataTable srcTable)
    03 {
    04     HSSFWorkbook workbook = new HSSFWorkbook();
    05     HSSFSheet sheet = workbook.CreateSheet();
    06     HSSFRow headerRow = sheet.CreateRow(0);
    07  
    08     // handling header.
    09     foreach (DataColumn column in srcTable.Columns)
    10         headerRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);
    11  
    12     // handling value.
    13     int rowIndex = 1;
    14  
    15     foreach (DataRow row in srcTable.Rows)
    16     {
    17         HSSFRow dataRow = sheet.CreateRow(rowIndex);
    18  
    19         foreach (DataColumn column in srcTable.Columns)
    20         {
    21            dataRow.CreateCell(column.Ordinal).SetCellValue(row[column].ToString());
    22         }
    23  
    24         rowIndex++;
    25     }
    26  
    27     MemoryStream stream = new MemoryStream();
    28     workbook.Write(stream);
    29     stream.Flush();
    30     stream.Position = 0;
    31  
    32     sheet = null;
    33     headerRow = null;
    34     workbook = null;
    35  
    36     return stream;
    37 }

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