2014-04-08 702 views
19

我正在使用NPOI將DataTable轉換爲ASP.NET Web API項目中的Excel。在NPOI workbook.write後,MemoryStream似乎會關閉?

但我沒有收到任何迴應。這裏是我的代碼:

public HttpResponseMessage GetExcelFromDataTable(DataTable dt) 
{ 
    IWorkbook workbook = new XSSFWorkbook(); // create *.xlsx file, use HSSFWorkbook() for creating *.xls file. 
    ISheet sheet1 = workbook.CreateSheet(); 
    IRow row1 = sheet1.CreateRow(0); 
    for (int i = 0; dt.Columns.Count > i; i++) 
    { 
     row1.CreateCell(i).SetCellValue(dt.Columns[i].ColumnName); 
    } 

    for (int i = 0; dt.Rows.Count > i; i++) 
    { 
     IRow row = sheet1.CreateRow(i + 1); 
     for (int j = 0; dt.Columns.Count > j; j++) 
     { 
      row.CreateCell(j).SetCellValue(dt.Rows[i][j].ToString()); 
     } 
    } 

    MemoryStream ms = new MemoryStream(); 
    workbook.Write(ms); 
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK); 
    result.Content = new StreamContent(ms); 
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); 
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); 
    result.Content.Headers.ContentDisposition.FileName = string.Format("{0}.xlsx", dt.TableName); 
    return result; 
} 

我設置一個斷點檢查workbook.Write(ms)ms.Length之後,但它返回一個例外:System.ObjectDisposedException

我哪裏錯了?

+12

NPOI的xlsx變體可以做到這一點,而另一種做不到。這只是圖書館的一個奇怪的東西,它有點糟糕。您可以通過執行ms.ToArray()並將其添加到新的MemoryStream中來解決此問題,但這有點令人傷心和浪費。 – alun

+1

@alun謝謝你,它的工作原理!但是,像你說的那樣真的很浪費......我會將它標記爲一種工作,並且看看它是否可以在未來得到解決。再次感謝你。 – fankt

+0

@alun謝謝。我使用的是生成xlsx的stream.getbuffer(),但在MS Excel中給出了「...損壞的數據..」消息。將stream.getbuffer更改爲ms.ToArray()修復了問題。 – maddog

回答

3

由於alun如上所述,並在this問題,你可以喂流進入另一個MemoryStream的:

... 
MemoryStream ms = new MemoryStream(); 
using(MemoryStream tempStream = new MemoryStream) 
{ 
    workbook.Write(tempStream); 
    var byteArray = tempStream.ToArray(); 
    ms.Write(byteArray, 0, byteArray.Length); 
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK); 
    result.Content = new StreamContent(ms); 
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); 
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); 
    result.Content.Headers.ContentDisposition.FileName = string.Format("{0}.xlsx", dt.TableName); 
    return result; 
} 

有不必這樣做有點代碼味道。但是,這僅在輸出.xlsx文件時有必要,因爲涉及的第三方庫處理流。

+0

我不得不說,我喜歡這個解決方案比覆蓋班級更好,因爲它更乾淨。 –

2

我遇到了類似的問題,關閉/處置它們不支持的流。我不熟悉NPOI,但我認爲Write方法正在接受Stream,而不是MemoryStream。如果是這樣的話,你可以創建一個包裝流類,它將所有的調用(讀/寫/查找等)轉發到內部流(在這種情況下是你的MemoryStream),但不轉發調用來關閉/處理。將包裝傳遞給Write方法,當它返回時,你的MemoryStream應該包含所有的內容,並且仍然是「打開」的。

此外,您可能需要ms.Seek(0, SeekOrigin.Begin)。在寫入你的內存流之後,它將被放置在流的末尾,所以如果你試圖從那個位置讀取,它會顯示爲空。

+1

+1,這個效果很好,是最有記憶力的,並且在情況再次出現時呈現可重用類。只需幾分鐘的編碼,您就可以覆蓋和轉發所有的流方法(除了'Close'和'Dispose' - 將這些方法留空爲NO-OP)到「包裝」流。我打電話給我'NoCloseStream',以便我確切知道它在做什麼。 –

15

對此問題的另一個解決方法...它不使用多個MemoryStream對象。

創建一個NpoiMemoryStream類繼承MemoryStream,並重寫了Close方法:

public class NpoiMemoryStream : MemoryStream 
{ 
    public NpoiMemoryStream() 
    { 
     AllowClose = true; 
    } 

    public bool AllowClose { get; set; } 

    public override void Close() 
    { 
     if (AllowClose) 
      base.Close(); 
    } 
} 

然後,使用該流是這樣的:

var ms = new NpoiMemoryStream(); 
ms.AllowClose = false; 
workbook.Write(ms); 
ms.Flush(); 
ms.Seek(0, SeekOrigin.Begin); 
ms.AllowClose = true; 

在沖水之間的某一點,並尋求,NPOI將嘗試關閉該流,但由於我們覆蓋了Close()並且AllowClose標誌爲false,因此我們可以保持該流打開。然後,將AllowClose設置回真,以便正常的處理機制可以關閉它。

不要誤解我的意思......這仍然是一個不應該被實施的黑客行爲......但從內存使用角度來看,它有點乾淨。