C# Core使用NPOI的XSSFWorkbook進行web開發中導出Excel


 之前也使用過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();
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM