上方廣告

Lady Kukki 手作糖霜餅乾

2012年10月18日 星期四

ASP.NET產生Excel的幾種方式

還記得剛跨進程式設計這個領域的時候,當時有處理到一個問題,使用者在Web上面產生Excel檔造成記憶體崩潰的問題。查看了程式發現那時候的開發方式是使用【 Excel 物件模型來存取】,但這種方式主要是運用在單機使用上,若在Web上面使用常常會有很多問題產生,例如 : 記憶體無法釋放等問題。

那時候剛進行,完全不知道該如何解決,老闆只好請我跟微軟詢問該如何解決,得到的答案確是Microsoft 不建議在Server端使用直接存取 Excel 物件模型的方式來控制 Excel 檔案,後來因為某些因素就離開了該公司,也沒有解決此問題,這個問題變成我寫程式以來最挫折的事情。但很幸運的是,後來所接觸的程式開發,除了報表外(可以透過ReportViewer幫我們產生),要匯出Excel的檔案都不是很複雜的,因此都可以使用Html表格畫表在透過改變ContentType的方式來產生,日子久了也漸漸淡忘這個悲慘的往事 ~"~。
這陣子再翻以前廠商開發的程式的時候發現了另一種使用方式NPOI Library,這是由幾個佛心來的開發者共同開發出來給大家使用的,詳細資訊可以參考MSDN的學習園地。下面大概說明一下三種常見的轉出Excel方式。

1. 透過改變ContentType為Excel標頭檔的方式,這個方法是最快速也最簡單,完全不需要使用使用額外的Library就可以轉出Excel檔,但缺點就是彈性很小,因為是使用HTML偽裝成Excel檔,若有比較特殊的需求就比較難達成,且轉出的檔案在新版的Office上面開啟會跳出警告訊息如下圖。
Response.Clear();
Response.AddHeader("content-disposition", "attachment;filename=Excel.xls");
Response.ContentType = "application/vnd.ms-excel";
Response.ContentEncoding = System.Text.Encoding.GetEncoding("utf-8");

Response.Write("<style type='text/css'>table{ vnd.ms-excel.numberformat:@;font-family:%u5FAE%u8EDF%u6B63%u9ED1%u9AD4;font-size:10pt; }</style>");
Response.Write("");
Response.Write("");

foreach (DataRow row in dt.Rows)
{
    Response.Write("");
    Response.Write("");
    Response.Write("");
    Response.Write("");
    Response.Write("");
}
Response.Write("<table border="1"><tr><td>Name</td><td>Birthday</td><td>Age</td></tr>");
Response.Write("<tr>");
Response.Write("<td>" + row["Name"].ToString() + "</td>");
Response.Write("<td>" + row["Birthday"].ToString() + "</td>");
Response.Write("<td>" + row["Age"].ToString() + "</td>");
Response.Write("</tr>");
Response.Write("</table>");
Response.End();


2. 使用Excel物件模型,這個就是我心中的痛~"~,但是由於在Web使用上不太適合,且有其它的替代方案,因此我就沒有深入研究了,關於Lock的更多的解決方是可以自行參考如何 回收 被 VB 及 C# 呼叫的 Excel 應用程式,簡單的使用方式如下。
protected void Button2_Click(object sender, EventArgs e)
{
    string execelfullpath = Server.MapPath("~/Excel.xls");

    Microsoft.Office.Interop.Excel.Application xlapp = new Microsoft.Office.Interop.Excel.Application();
    Microsoft.Office.Interop.Excel.Workbook wbook = xlapp.Workbooks.Add(1);
    Microsoft.Office.Interop.Excel.Worksheet wsheet = (Microsoft.Office.Interop.Excel.Worksheet)wbook.Worksheets[1];

    wsheet.Name = "Sheet Name";

    Microsoft.Office.Interop.Excel.Range range;

    //標題列
    wsheet.Cells[1, 1] = "Name";
    wsheet.Cells[1, 2] = "Birthday";
    wsheet.Cells[1, 3] = "Age";
    FormateExcelStyle((Microsoft.Office.Interop.Excel.Range)wsheet.Cells[1, 1]);
    FormateExcelStyle((Microsoft.Office.Interop.Excel.Range)wsheet.Cells[1, 2]);
    FormateExcelStyle((Microsoft.Office.Interop.Excel.Range)wsheet.Cells[1, 3]);

    int introw = 2;
    foreach (DataRow dr in dt.Rows)
    {
        wsheet.Cells[introw, 1] = dr["Name"].ToString();
        wsheet.Cells[introw, 2] = dr["Birthday"].ToString();
        wsheet.Cells[introw, 3] = dr["Age"].ToString();

        FormateExcelStyle((Microsoft.Office.Interop.Excel.Range)wsheet.Cells[introw, 1]);
        FormateExcelStyle((Microsoft.Office.Interop.Excel.Range)wsheet.Cells[introw, 2]);
        FormateExcelStyle((Microsoft.Office.Interop.Excel.Range)wsheet.Cells[introw, 3]);

        introw++;
    }

    if (File.Exists(execelfullpath)) File.Delete(execelfullpath);

    wbook.SaveAs(execelfullpath,
                                Microsoft.Office.Interop.Excel.XlFileFormat.xlXMLSpreadsheet,
                                Missing.Value,
                                Missing.Value,
                                false,
                                false,
                                Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlNoChange,
                                Missing.Value,
                                Missing.Value,
                                Missing.Value,
                                Missing.Value,
                                Missing.Value
    );

    KillExcel(xlapp);

    Response.Redirect("Excel.xls");
}

//儲存格格式
private void FormateExcelStyle(Microsoft.Office.Interop.Excel.Range range)
{
    range.Font.Name = "微軟正黑體";
    range.BorderAround(XlLineStyle.xlContinuous, XlBorderWeight.xlThin, XlColorIndex.xlColorIndexAutomatic, null);
}

//刪除Excel執行個體,解決Excel發生Lock的問題
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
static void KillExcel(Microsoft.Office.Interop.Excel.Application m_objExcel)
{
    if (m_objExcel != null)
    {
        int lpdwProcessId;
        GetWindowThreadProcessId(new IntPtr(m_objExcel.Hwnd), out lpdwProcessId);
        System.Diagnostics.Process.GetProcessById(lpdwProcessId).Kill();
        System.Threading.Thread.Sleep(2000);
    }
}

3. 使用NPOI Library,下面的使用方式所產生檔案是跟上面一模一樣的,這只是一個簡單的範例,NPOI功能強大且並沒有使用到 Excel 的任何東西,它直接深入 OLE Compound Document (複合文件)格式內去存取資料,也可以直接控制到各種儲存格的資訊(顏色,儲存格格式與樣式等),並將它物件導向化,外部開發人員只需要利用 NPOI 提供的屬性就可以控制 Office 格式的檔案資料,且更不會有Lock的問題。
HSSFWorkbook workbook = new HSSFWorkbook();
using (MemoryStream ms = new MemoryStream())
{
    // 新增試算表。
    ISheet sheet = workbook.CreateSheet("Sheet Name");

    // 儲存格字型
    NPOI.SS.UserModel.IFont font = workbook.CreateFont();
    font.FontName = "微軟正黑體";

    // 建立儲存格樣式。
    ICellStyle style = workbook.CreateCellStyle();
    style.SetFont(font);
    style.BorderBottom = NPOI.SS.UserModel.BorderStyle.THIN;
    style.BottomBorderColor = NPOI.HSSF.Util.HSSFColor.BLACK.index;
    style.BorderLeft = NPOI.SS.UserModel.BorderStyle.THIN;
    style.LeftBorderColor = NPOI.HSSF.Util.HSSFColor.BLACK.index;
    style.BorderRight = NPOI.SS.UserModel.BorderStyle.THIN;
    style.RightBorderColor = NPOI.HSSF.Util.HSSFColor.BLACK.index;
    style.BorderTop = NPOI.SS.UserModel.BorderStyle.THIN;
    style.TopBorderColor = NPOI.HSSF.Util.HSSFColor.BLACK.index;

    //插入標題列
    IRow row = sheet.CreateRow(0);
    row.CreateCell(0).SetCellValue("Name");
    row.CreateCell(1).SetCellValue("Birthday");
    row.CreateCell(2).SetCellValue("Age");
    row.Cells[0].CellStyle = style;
    row.Cells[1].CellStyle = style;
    row.Cells[2].CellStyle = style;

    int i = 1;
    foreach (DataRow dr in dt.Rows)
    {
        row = sheet.CreateRow(i);
        row.CreateCell(0).SetCellValue(dr["Name"].ToString());
        row.CreateCell(1).SetCellValue(dr["Birthday"].ToString());
        row.CreateCell(2).SetCellValue(dr["Age"].ToString());
        row.Cells[0].CellStyle = style;
        row.Cells[1].CellStyle = style;
        row.Cells[2].CellStyle = style;
        i++;
    }

    workbook.Write(ms);
    Response.AddHeader("Content-Disposition", string.Format("attachment; filename=Excel.xls"));
    Response.BinaryWrite(ms.ToArray());
}

workbook = null;

這樣比較下來NPOI幾乎跟上面兩種方使比起來幾乎是零缺點,但是我還是不常使用到它~"~,因為說真的,專案開發時程的限制,讓自己沒辦法追求到完美,有時候一些很簡單的清單,我就會用偷懶的方式Response.Write來處理了。

本文範例 :
ExprotExcel.zip