資料匯出 Excel 是企業應用的剛需功能。本文系統整理 ASP.NET Web 與 WinForms 桌面應用的多種匯出方案,比較 Response.OutputStream、NPOI、EPPlus、ClosedXML、COM Interop 等技術優劣,提供選型指南與最佳實踐。
public void ExportHtmlTable(DataTable dt, string fileName)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.Buffer = true;
HttpContext.Current.Response.Charset = "UTF-8";
HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.UTF8;
HttpContext.Current.Response.AppendHeader("Content-Disposition",
$"attachment;filename={fileName}.xls");
HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
StringWriter sw = new StringWriter();
HtmlTextWriter hw = new HtmlTextWriter(sw);
// 建立 GridView 繫結資料
GridView gv = new GridView();
gv.DataSource = dt;
gv.DataBind();
gv.RenderControl(hw);
HttpContext.Current.Response.Write(sw.ToString());
HttpContext.Current.Response.End();
}
優點:無第三方依賴、支援樣式、瀏覽器直接開啟
缺點:實為 HTML 偽裝 Excel、大數據效能差、無法公式/圖表
public void ExportCsv(DataTable dt, string fileName)
{
var response = HttpContext.Current.Response;
response.Clear();
response.Buffer = true;
response.Charset = "UTF-8";
response.ContentEncoding = Encoding.GetEncoding("GB2312"); // 中文相容
response.AppendHeader("Content-Disposition", $"attachment;filename={fileName}.csv");
response.ContentType = "application/csv";
StringBuilder sb = new StringBuilder();
// 標題列
for (int i = 0; i < dt.Columns.Count; i++)
{
sb.Append(dt.Columns[i].ColumnName);
if (i < dt.Columns.Count - 1) sb.Append(",");
}
sb.AppendLine();
// 資料列
foreach (DataRow row in dt.Rows)
{
for (int i = 0; i < dt.Columns.Count; i++)
{
string value = row[i]?.ToString()?.Replace("\"", "\"\"") ?? "";
if (value.Contains(",") || value.Contains("\"") || value.Contains("\n"))
value = $"\"{value}\"";
sb.Append(value);
if (i < dt.Columns.Count - 1) sb.Append(",");
}
sb.AppendLine();
}
response.Write(sb.ToString());
response.End();
}
// NuGet: Install-Package NPOI
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel; // .xlsx
using NPOI.HSSF.UserModel; // .xls
public void ExportNpoi(DataTable dt, string fileName, bool isXlsx = true)
{
IWorkbook workbook = isXlsx ? new XSSFWorkbook() : new HSSFWorkbook();
ISheet sheet = workbook.CreateSheet("Sheet1");
// 樣式定義
ICellStyle headerStyle = workbook.CreateCellStyle();
headerStyle.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Grey25Percent.Index;
headerStyle.FillPattern = FillPattern.SolidForeground;
IFont headerFont = workbook.CreateFont();
headerFont.Boldweight = (short)FontBoldWeight.Bold;
headerStyle.SetFont(headerFont);
// 標題列
IRow headerRow = sheet.CreateRow(0);
for (int i = 0; i < dt.Columns.Count; i++)
{
ICell cell = headerRow.CreateCell(i);
cell.SetCellValue(dt.Columns[i].ColumnName);
cell.CellStyle = headerStyle;
}
// 資料列
for (int r = 0; r < dt.Rows.Count; r++)
{
IRow row = sheet.CreateRow(r + 1);
for (int c = 0; c < dt.Columns.Count; c++)
{
ICell cell = row.CreateCell(c);
object value = dt.Rows[r][c];
SetCellValue(cell, value);
}
}
// 自動欄寬
for (int i = 0; i < dt.Columns.Count; i++)
sheet.AutoSizeColumn(i);
// 輸出
var response = HttpContext.Current.Response;
response.Clear();
response.ContentType = isXlsx ?
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" :
"application/vnd.ms-excel";
response.AppendHeader("Content-Disposition", $"attachment;filename={fileName}.{(isXlsx ? "xlsx" : "xls")}");
using (MemoryStream ms = new MemoryStream())
{
workbook.Write(ms);
response.BinaryWrite(ms.ToArray());
}
response.End();
}
private void SetCellValue(ICell cell, object value)
{
switch (value)
{
case null: cell.SetCellValue(""); break;
case DateTime dt: cell.SetCellValue(dt); break;
case bool b: cell.SetCellValue(b); break;
case double d: cell.SetCellValue(d); break;
case int i: cell.SetCellValue(i); break;
case decimal dec: cell.SetCellValue((double)dec); break;
default: cell.SetCellValue(value.ToString()); break;
}
}
// NuGet: Install-Package EPPlus
// 或: Install-Package ClosedXML
using OfficeOpenXml; // EPPlus
// using ClosedXML.Excel; // ClosedXML
public void ExportEpPlus(DataTable dt, string fileName)
{
ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // EPPlus 5+ 需設定
using (var package = new ExcelPackage())
{
var ws = package.Workbook.Worksheets.Add("Sheet1");
// 從 DataTable 載入 (EPPlus 核心功能)
ws.Cells["A1"].LoadFromDataTable(dt, true, TableStyles.Light1);
// 自動欄寬
ws.Cells.AutoFitColumns();
var response = HttpContext.Current.Response;
response.Clear();
response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
response.AppendHeader("Content-Disposition", $"attachment;filename={fileName}.xlsx");
response.BinaryWrite(package.GetAsByteArray());
response.End();
}
}
// ClosedXML 版本 (語法更直觀)
public void ExportClosedXml(DataTable dt, string fileName)
{
using (var wb = new XLWorkbook())
{
var ws = wb.Worksheets.Add(dt, "Sheet1");
ws.Columns().AdjustToContents();
var response = HttpContext.Current.Response;
response.Clear();
response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
response.AppendHeader("Content-Disposition", $"attachment;filename={fileName}.xlsx");
using (var ms = new MemoryStream())
{
wb.SaveAs(ms);
response.BinaryWrite(ms.ToArray());
}
response.End();
}
}
同 ASP.NET 核心邏輯,僅輸出改為檔案儲存:
public void ExportToFile(DataTable dt, string filePath)
{
using (var wb = new XLWorkbook())
{
wb.Worksheets.Add(dt, "Sheet1");
wb.Worksheets.First().Columns().AdjustToContents();
wb.SaveAs(filePath);
}
MessageBox.Show($"匯出完成:{filePath}");
}
using Excel = Microsoft.Office.Interop.Excel;
public void ExportWithInterop(DataTable dt, string filePath)
{
Excel.Application excel = null;
Excel.Workbook workbook = null;
Excel.Worksheet worksheet = null;
try
{
excel = new Excel.Application();
excel.Visible = false;
excel.DisplayAlerts = false;
workbook = excel.Workbooks.Add();
worksheet = (Excel.Worksheet)workbook.ActiveSheet;
worksheet.Name = "Report";
// 寫入標題
for (int c = 0; c < dt.Columns.Count; c++)
{
worksheet.Cells[1, c + 1] = dt.Columns[c].ColumnName;
Excel.Range headerCell = (Excel.Range)worksheet.Cells[1, c + 1];
headerCell.Font.Bold = true;
headerCell.Interior.ColorIndex = 15;
}
// 批次寫入資料 (陣列方式,效能佳)
object[,] data = new object[dt.Rows.Count, dt.Columns.Count];
for (int r = 0; r < dt.Rows.Count; r++)
for (int c = 0; c < dt.Columns.Count; c++)
data[r, c] = dt.Rows[r][c];
Excel.Range range = worksheet.get_Range("A2",
worksheet.Cells[dt.Rows.Count + 1, dt.Columns.Count]);
range.Value2 = data;
// 格式化
range.Borders.LineStyle = Excel.XlLineStyle.xlContinuous;
range.Columns.AutoFit();
workbook.SaveAs(filePath);
}
finally
{
// 正確釋放 COM 物件 (關鍵!)
ReleaseComObject(worksheet);
ReleaseComObject(workbook);
if (excel != null)
{
excel.Quit();
ReleaseComObject(excel);
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
private void ReleaseComObject(object obj)
{
try
{
if (obj != null)
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
}
catch { }
finally { obj = null; }
}
| 方案 | 相依性 | 檔案格式 | 效能 | 功能豐富度 | 適用場景 |
|---|---|---|---|---|---|
| HTML 輸出 | 無 | .xls (偽) | 低 | 基本樣式 | 簡單報表、快速原型 |
| CSV | 無 | .csv | 最高 | 無樣式 | 大量資料、資料交換 |
| NPOI | NPOI.dll | .xls/.xlsx | 高 | 完整 | 伺服器端、無 Office 環境 |
| EPPlus | EPPlus.dll | .xlsx | 高 | 豐富 | 現代專案、.NET Core |
| ClosedXML | ClosedXML.dll | .xlsx | 高 | 豐富、API 友善 | 推薦新專案首選 |
| COM Interop | Excel 安裝 | .xls/.xlsx | 低 | 最完整 (巨集、圖表、樞紐) | 需 Excel 進階功能、客戶端 |
Range.Value2 = object[,]excel.Calculation = XlCalculation.xlCalculationManualexcel.ScreenUpdating = false| 問題 | 原因 | 解決方案 |
|---|---|---|
| 匯出中文亂碼 | 編碼不匹配 | CSV 用 GB2312/UTF-8+BOM;Excel 用正確 ContentType |
| 大檔案記憶體溢位 | 資料全載入記憶體 | 分頁查詢、串流寫入、NPOI SXSSF (串流模式) |
| 伺服器無 Excel 授權 | COM Interop 需授權 | 改用 NPOI/EPPlus/ClosedXML |
| 日期格式錯誤 | 儲存為字串非日期型別 | SetCellValue 判斷 DateTime 型別設定 CellStyle |
| 公式不計算 | 非 Excel 引擎 | NPOI 需呼叫 ForceFormulaRecalculation |
// 介面抽象,支援多種實作
public interface IExcelExporter
{
byte[] Export(DataTable data, ExportOptions options);
void ExportToFile(DataTable data, string path, ExportOptions options);
}
public class ExportOptions
{
public string SheetName { get; set; } = "Sheet1";
public bool AutoFitColumns { get; set; } = true;
public bool IncludeHeaders { get; set; } = true;
public Dictionary ColumnFormats { get; set; }
}
// 工廠模式選擇實作
public static class ExcelExporterFactory
{
public static IExcelExporter Create(ExportEngine engine)
{
return engine switch
{
ExportEngine.NPOI => new NpoiExporter(),
ExportEngine.EPPlus => new EpPlusExporter(),
ExportEngine.ClosedXML => new ClosedXmlExporter(),
ExportEngine.Csv => new CsvExporter(),
_ => throw new ArgumentException("不支援的引擎")
};
}
}
Excel 匯出技術選型取決於:部署環境 (有無 Office)、檔案格式需求 (.xls/.xlsx/.csv)、功能複雜度 (樣式/公式/圖表)、資料量大小。現代專案首推 ClosedXML 或 EPPlus:無 Office 相依、API 直覺、支援 .NET Core、效能優異。大量資料用 CSV 或 NPOI SXSSF 串流模式。COM Interop 僅在必須使用 Excel 獨有功能 (巨集、樞紐分析、圖表) 時採用,並嚴格管理 COM 物件釋放。