web-dev-qa-db-ja.com

NPOI workbook.writeの後でMemoryStreamが閉じているようです。

[〜#〜] npoi [〜#〜] を使用して、ASP.NET Web APIプロジェクトでDataTableをExcelに変換しています。

しかし、私は応答から何も得ませんでした。これが私のコードです:

_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_。

どこで私は間違えましたか?

28
fankt

この問題の別の回避策...複数のMemoryStreamオブジェクトを使用しない。

NpoiMemoryStreamを継承するMemoryStreamクラスを作成し、Closeメソッドをオーバーライドします。

_public class NpoiMemoryStream : MemoryStream
{
    public NpoiMemoryStream()
    {
        // We always want to close streams by default to
        // force the developer to make the conscious decision
        // to disable it.  Then, they're more apt to remember
        // to re-enable it.  The last thing you want is to
        // enable memory leaks by default.  ;-)
        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をtrueに戻して、通常の廃棄メカニズムでクローズできるようにします。

誤解しないでください...これはまだ実装する必要のないハックです...しかし、メモリ使用の観点からは少しクリーンです。

28
Joe the Coder

上記の 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ファイルを出力する場合にのみ必要です。

7
Josh Stella

所有していないストリームを閉じる/破棄するAPIで同様の問題が発生しました。私はNPOIに精通していませんが、WriteメソッドはMemoryStreamではなくStreamを受け入れていると思います。その場合は、すべての呼び出し(読み取り/書き込み/シークなど)を内部ストリーム(この場合はMemoryStream)に転送するが、呼び出しをclose/disposeに転送しないラッパーStreamクラスを作成できます。ラッパーをWriteメソッドに渡します。これが返されると、MemoryStreamはすべてのコンテンツを含み、まだ「開いている」はずです。

さらに、おそらくms.Seek(0, SeekOrigin.Begin)が必要になります。 Writeの呼び出し後、メモリストリームはストリームの最後に配置されるため、その位置から読み取ろうとすると、空になります。

2
MarkPflug

これがまだ必要かどうかはわかりませんが、overloadがあります

Write(Stream stream, bool leaveOpen)

ここで、leaveOpen = true、MemoryStreamを開いたままにします

0