NPOI 是 Apache POI 的 .NET 移植版,純 Managed 程式碼、**無需安裝 Office**、**支援 .xls/.xlsx**、跨平台 (Windows/Linux/macOS),是 ASP.NET Core 專案處理 Excel 的首選庫。
Install-Package NPOI
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel; // .xlsx
using NPOI.HSSF.UserModel; // .xls
using System.Data;
public class ExcelExporter
{
public byte[] ExportToXlsx(DataTable dt, string sheetName = "Sheet1")
{
using (var workbook = new XSSFWorkbook()) // .xlsx
{
var sheet = workbook.CreateSheet(sheetName);
// 標題列
var headerRow = sheet.CreateRow(0);
for (int i = 0; i < dt.Columns.Count; i++)
{
headerRow.CreateCell(i).SetCellValue(dt.Columns[i].ColumnName);
// 設定標題樣式 (粗體、背景色)
var style = workbook.CreateCellStyle();
var font = workbook.CreateFont(); font.IsBold = true;
style.SetFont(font);
style.FillForegroundColor = IndexedColors.Grey25Percent.Index;
style.FillPattern = FillPattern.SolidForeground;
headerRow.Cells[i].CellStyle = style;
}
// 資料列
for (int r = 0; r < dt.Rows.Count; r++)
{
var row = sheet.CreateRow(r + 1);
for (int c = 0; c < dt.Columns.Count; c++)
{
var cell = row.CreateCell(c);
object val = dt.Rows[r][c];
SetCellValue(cell, val);
}
}
// 自動調整欄寬
for (int i = 0; i < dt.Columns.Count; i++) sheet.AutoSizeColumn(i);
using (var ms = new MemoryStream())
{
workbook.Write(ms);
return ms.ToArray();
}
}
}
private void SetCellValue(ICell cell, object value)
{
if (value == null || value == DBNull.Value) { cell.SetCellValue(""); return; }
switch (value)
{
case string s: cell.SetCellValue(s); break;
case int i: cell.SetCellValue(i); break;
case long l: cell.SetCellValue(l); break;
case double d: cell.SetCellValue(d); break;
case decimal d: cell.SetCellValue((double)d); break;
case DateTime dt: cell.SetCellValue(dt); break;
case bool b: cell.SetCellValue(b); break;
default: cell.SetCellValue(value.ToString()); break;
}
}
}
[HttpGet]
public IActionResult ExportMembers()
{
var dt = _memberService.GetExportData(); // 取得 DataTable
var bytes = new ExcelExporter().ExportToXlsx(dt, "會員名單");
string fileName = $"會員名單_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
}
[HttpPost]
public async Task ImportMembers(IFormFile file)
{
if (file == null || file.Length == 0) return BadRequest("請選擇檔案");
if (!file.FileName.EndsWith(".xlsx", StringComparison.OrdinalIgnoreCase))
return BadRequest("僅支援 .xlsx 格式");
using (var stream = file.OpenReadStream())
using (var workbook = new XSSFWorkbook(stream))
{
var sheet = workbook.GetSheetAt(0); // 第一個工作表
if (sheet == null) return BadRequest("空的工作表");
// 驗證標題列
var headerRow = sheet.GetRow(0);
if (headerRow == null) return BadRequest("缺少標題列");
string[] expectedHeaders = { "姓名", "簡稱", "分類", "參考價", "商品介紹" };
for (int i = 0; i < expectedHeaders.Length; i++)
{
if (headerRow.GetCell(i)?.ToString() != expectedHeaders[i])
return BadRequest($"第 {i+1} 欄標題應為 '{expectedHeaders[i]}'");
}
var products = new List();
for (int r = 1; r <= sheet.LastRowNum; r++) // LastRowNum 為最後一行索引 (0-based)
{
var row = sheet.GetRow(r);
if (row == null) continue; // 空行跳過
if (row.Cells.All(c => c == null || string.IsNullOrWhiteSpace(c.ToString()))) continue; // 全空行跳過
var product = new Product
{
Name = row.GetCell(0)?.ToString()?.Trim(),
ShortName = row.GetCell(1)?.ToString()?.Trim(),
Category = row.GetCell(2)?.ToString()?.Trim(),
RefPrice = decimal.TryParse(row.GetCell(3)?.ToString(), out var p) ? p : 0,
Description = row.GetCell(4)?.ToString()?.Trim()
};
// 業務驗證
if (string.IsNullOrEmpty(product.Name)) continue; // 或回傳錯誤列表
if (!_categoryService.Exists(product.Category)) continue; // 分類不存在跳過
products.Add(product);
}
if (products.Count > 0)
await _productService.BulkInsertAsync(products);
return Ok(new { Imported = products.Count, Message = "匯入完成" });
}
}
| 項目 | 說明 |
|---|---|
| Workbook 選擇 | `XSSFWorkbook` (.xlsx, OOXML) / `HSSFWorkbook` (.xls, BIFF8) / `WorkbookFactory.Create(stream)` 自動偵測 |
| 儲存格索引 | 0-based (行、列皆從 0 開始) |
| 日期處理 | `cell.SetCellValue(DateTime)` + `cell.CellStyle.DataFormat = workbook.CreateDataFormat().GetFormat("yyyy/mm/dd")` |
| 公式 | `cell.SetCellFormula("A1+B1")`,讀取時 `cell.CachedFormulaResultType` 判斷 |
| 大量資料效能 | 使用 `SXSSFWorkbook` (串流式,僅保留滾動窗口在記憶體),適用 10萬列以上 |
| 樣式重用 | 建立一次 `ICellStyle` 重複套用,避免建立過多 Style 導致檔案損毀 (限制 64000 個) |
| 記憶體管理 | `using` 區塊確保 `Workbook`、`Stream` 正確釋放 |
| 庫 | 授權 | .xlsx | .xls | 效能 | 功能 | 跨平台 |
|---|---|---|---|---|---|---|
| NPOI | Apache 2.0 | ✅ | ✅ | 中 | 全 | ✅ |
| ClosedXML | MIT | ✅ | ❌ | 中 | 豐富 (LINQ 風格) | ✅ |
| EPPlus | Polyform Noncommercial (商業需付費) | ✅ | ❌ (5.0+) | 高 | 最豐富 (樣式、圖表、樞紐) | ✅ |
| Open XML SDK | MIT | ✅ | ❌ | 高 (底層) | 基礎 (需自行組裝 XML) | ✅ |
NPOI 以免費開源、雙格式支援、跨平台相容,成為 ASP.NET Core 處理 Excel 的標準選擇。掌握 `XSSFWorkbook`、`SXSSFWorkbook` (大數據)、樣式重用與儲存格類型對應,即可覆蓋 95% 匯入匯出需求。