NPOI是C#导出Excel的推荐方案,纯.NET实现、跨平台、无Office依赖;避免使用Interop,因其在服务器环境易崩溃;导出时用MemoryStream、正确设置响应头、单元格样式和数据格式。

用 NPOI 导出 Excel,别碰 Microsoft.Office.Interop.Excel
服务器环境跑不起来 Interop,它依赖本地 Office 安装、有进程残留、并发一高就卡死或崩溃。NPOI 是纯 .NET 实现,跨平台、无依赖、内存操作快,是 C# 导出 Excel 的事实标准。
常见错误现象:COMException: 检索 COM 类工厂中 CLSID 为 {00024500-0000-0000-C000-000000000046} 的组件失败——这就是 Interop 在服务器上跪了的典型报错。
- 用
NuGet安装NPOI(注意选netstandard2.0或对应框架版本) - 导出
.xlsx用XSSFWorkbook,导出老版.xls用HSSFWorkbook(已淘汰,别用) - 不要手动 new
FileStream写磁盘再读取——直接用MemoryStream构造响应流,避免临时文件和 IO 瓶颈
DataTable 转 XSSFWorkbook:表头和空值要单独处理
DataTable 列名默认当表头,但字段类型、空值、DateTime 格式不处理会导出成乱码或 1900/1/1。NPOI 不自动识别数据类型,得手动设单元格样式。
使用场景:后台管理列表页“导出全部”,数据来自 SQL 查询结果,含 DBNull.Value、DateTime?、bool 等可空类型。
- 遍历
DataTable.Columns写第一行,用cell.SetCellValue(column.ColumnName) - 遍历
DataRow时,对每个value做== DBNull.Value判断,跳过SetCellValue(null)(会抛异常) -
DateTime类型建议统一转成字符串(如ToString("yyyy-MM-dd HH:mm")),或提前创建DataFormat并绑定到单元格样式 - 数值列如果导出后显示为“常规”格式导致小数丢失,需显式调用
cell.CellStyle.DataFormat = workbook.CreateDataFormat().GetFormat("0.00")
List 导出时,属性名大小写和中文字段名要小心
反射取 PropertyInfo 默认按声明顺序,但没做排序;若类里字段顺序乱、含 [DisplayName] 或自定义特性,NPOI 不识别,得自己处理。
性能影响:每次导出都反射所有属性,高频导出建议缓存 PropertyInfo[] 数组,尤其当 T 是复杂模型时。
- 用
typeof(T).GetProperties()获取属性,但加.OrderBy(p => p.MetadataToken)无法保证顺序稳定,推荐用[Index(0)]自定义特性 + 排序 - 属性名为中文?可以,
cell.SetCellValue(prop.GetValue(item)?.ToString())没问题,但 Excel 打开后列宽可能极窄,需调用sheet.AutoSizeColumn(colIndex)(注意:只对已写入数据的列生效) - 含只读属性(
getonly)或计算属性?确保其值不为null,否则ToString()可能抛NullReferenceException
ASP.NET Core 返回 Excel 文件下载:Content-Disposition 和 MIME 类型不能错
浏览器不弹保存框、直接打开乱码页、文件名变 download.xlsx——八成是响应头设错了。MIME 类型不是 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 就是错的。
兼容性影响:旧版 IE 需要 application/octet-stream,但现在基本不用考虑;重点是 Content-Disposition 必须带 filename*=UTF-8''xxx.xlsx 才支持中文名。
- 用
MemoryStream写完workbook.Write(stream)后,stream.Position = 0,否则返回空文件 - 响应头必须设:
Content-Disposition: attachment; filename="data.xlsx"; filename*=UTF-8''data.xlsx - MIME 类型固定用:
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet(不是application/xlsx,也不是application/excel) - 别用
FileResult直接传路径——你根本没有物理文件,传FileStreamResult也得包装MemoryStream,不如直接用FileContentResult
最易被忽略的是:NPOI 的 CellStyle 对象不能跨行复用,每次新建单元格都要新 CreateCellStyle() 或从已有样式克隆;还有 AutoSizeColumn 在大数据量时会明显拖慢,建议只对前 10 列关键字段调用。











