之前也使用過NPOI導出excel,這次是因為在導出的excel里新增了幾個列,正好超出了255的限制,所以又要改了。今天主要出了4個問題:
1. Invalid column index (256). Allowable column range for BIFF8 is (0..255) or ('A'..'IV')
2.c# npoi XSSFWorkbook無法訪問已關閉的流。
3.由於代碼已經過優化或者本機框架位於調用堆棧之上,無法計算表達式的值。
4.錯誤提示: Excel在“春天Excel2007.xlsx”中發現不可讀取內容。是否恢復工作簿的內容?如果信任此工作簿的來源,請單擊“是”。 單擊“是”后:Excel 已完成文件級驗證和修復。此工作簿的某些部分可能已被修復或丟棄。
經歷了一上午,不斷百度,進行了很多次嘗試,也經歷了幾次項目的平衡,所以做一個總結。之前的導出是這樣的,使用hssf方法,代碼如下:
public static MemoryStream RenderToExcel(DataTable table)
{
MemoryStream ms = new MemoryStream();
using (table)
{
IWorkbook workbook = new HSSFWorkbook();
ISheet sheet = workbook.CreateSheet();
IRow headerRow = sheet.CreateRow(0);
foreach (DataColumn column in table.Columns)
{
var headCell = headerRow.CreateCell(column.Ordinal);
headCell.SetCellValue(column.Caption);
}
int rowIndex = 1;
foreach (DataRow row in table.Rows)
{
IRow dataRow = sheet.CreateRow(rowIndex);
foreach (DataColumn column in table.Columns)
{
var cellell = dataRow.CreateCell(column.Ordinal);
cellell.SetCellValue(row[column].ToString());
}
rowIndex++;
}
workbook.Write(ms);
ms.Flush();
ms.Position = 0;
}
return ms;
}
這樣導出的是“.XLS”格式的excel表格,所以在列數超過256之后就會提示第一個提示錯誤。上網百度,找了很多,參考https://www.cnblogs.com/jRoger/p/4602239.html一篇文章,說是office2007之后,可以創建的列數大多了,才才用了XSSF的方法。
剛開始直接將hssf換成xssf,結果提示第二個錯誤,無法訪問已關閉的流。再百度,參考https://www.cnblogs.com/caoyc/p/6215210.html文章,說NPOI生產.xlsx文件件時,在使用book.Write(ms);后,會關閉流,這樣導致再次使用Respons輸出流的時候就出錯了。使用自定義流代替內存流,這樣就可以保證流不會被自定關閉了。改寫代碼如下:
public class NPOIMemoryStream : MemoryStream
{
/// <summary>
/// 獲取流是否關閉
/// </summary>
public bool IsColse
{
get;
private set;
}
public NPOIMemoryStream(bool colse = false)
{
IsColse = colse;
}
public override void Close()
{
if (IsColse)
{
base.Close();
}
}
}
public static MemoryStream RenderToExcelNew(DataTable table)
{
NPOIMemoryStream ms = new NPOIMemoryStream();
using (table)
{
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = null;
sheet =workbook.CreateSheet();
IRow headerRow = sheet.CreateRow(0);
foreach (DataColumn column in table.Columns)
{
var headCell = headerRow.CreateCell(column.Ordinal);
headCell.SetCellValue(column.Caption);
}
int rowIndex = 1;
foreach (DataRow row in table.Rows)
{
IRow dataRow = sheet.CreateRow(rowIndex);
foreach (DataColumn column in table.Columns)
{
var cellell = dataRow.CreateCell(column.Ordinal);
cellell.SetCellValue(row[column].ToString());
}
rowIndex++;
}
workbook.Write(ms);
ms.Flush();
ms.Position = 0;
}
return ms;
}
注意上面是新建了一個NPOIMemoryStream類,它繼承於MemoryStream。下面的導出方法還是和之前的類似。有興趣的讀者,可以將它們合並成
if (fileName.IndexOf(".xlsx") > 0) // 2007版本
workbook = new XSSFWorkbook(fs);
else if (fileName.IndexOf(".xls") > 0) // 2003版本
workbook = new HSSFWorkbook(fs);
這樣的形式。
另外在這個過程中,因為項目中引用NPOI的位置有兩個,導致兩個加載之后的版本不一致,報這樣的錯誤“未能加載文件或程序集“ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73”或它的某一個依賴項”。於是一不做二不休,將文件全部啟用NuGet程序包,來獲取NPOI。雖然它的速度特別慢。但是由於之前在項目中新建了文件夾存放4個NPOI的dll,導致有一個項目的引用始終不是NuGet的程序包。當時還一度想放棄了,我把列數降下來不就行啦,費那老鼻子勁干嘛。中午午休之后,想了想,自己還是把這個問題解決吧。於是仔細看了dll引用路徑之后,將項目的npoi文件夾刪除了,果然引用就正確了。
我的導出是流的形式,生成之后導出的代碼是:
using (MemoryStream ms = RenderToExcelNew(table))
{
RenderToBrowser(ms, context, fileName);
}
public static void RenderToBrowser(MemoryStream ms, HttpContext context, string fileName)
{
context.Response.Clear();
context.Response.ContentEncoding = Encoding.GetEncoding("GB2312");
context.Response.ContentType = "application/octet-stream";
context.Response.AddHeader("Content-Disposition", "attachment;fileName=" + fileName);
context.Response.BinaryWrite(ms.ToArray());
context.Response.Flush();
context.Response.End();
}
這樣就出現了第3個問題,“由於代碼已經過優化或者本機框架位於調用堆棧之上,無法計算表達式的值。”,異常報錯在最后一行,Response.End() 由於代碼已經過優化或者本機框架位於調用堆棧之上,無法計算表達式的值。再百度吧,看到https://www.cnblogs.com/Alben-wang/p/6096568.html,於是將Response.End,改成調用HttpContext.Current.ApplicationInstance.CompleteRequest方法。於是最后一行為context.ApplicationInstance.CompleteRequest();。
但還是有問題,運行了一遍程序之后,就爆出了第4個錯誤。文件導出之后,提示:Excel在“xxxxxx.xlsx”中發現不可讀取內容。是否恢復工作簿的內容?如果信任此工作簿的來源,請單擊“是”。 單擊“是”后:Excel 已完成文件級驗證和修復。此工作簿的某些部分可能已被修復或丟棄。再百度吧,看到了https://www.cnblogs.com/qk2014/p/7729215.html,這篇文章,介紹的很好。加上設置大小下載下來的.xlsx文件打開時才不會報“Excel 已完成文件級驗證和修復。此工作簿的某些部分可能已被修復或丟棄”:
long fileSize = ms.Length;
context.Response.AddHeader("Content-Length", fileSize.ToString());
即可。於是最后的代碼就變成了:
public static void RenderToBrowser(MemoryStream ms, HttpContext context, string fileName)
{
context.Response.Clear();
context.Response.ContentEncoding = Encoding.GetEncoding("GB2312");
context.Response.ContentType = "application/octet-stream";
context.Response.AppendHeader("Content-Disposition", "attachment; filename=" + fileName);
long fileSize = ms.Length;
context.Response.AddHeader("Content-Length", fileSize.ToString());
context.Response.BinaryWrite(ms.ToArray());
context.Response.Flush();
context.ApplicationInstance.CompleteRequest();
}
