Подтвердить что ты не робот

MemoryStream, кажется, закрыт после NPOI workbook.write?

Я использую NPOI для преобразования DataTable в Excel в проект веб-API ASP.NET.

Но я ничего не получил от ответа. Здесь мой код:

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;
}

Я установил точку прерывания для проверки ms.Length после workbook.Write(ms), но он возвращает исключение: System.ObjectDisposedException.

Где я ошибся?

4b9b3361

Ответ 1

Еще одно решение этой проблемы... которое не использует несколько объектов 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, чтобы обычные механизмы удаления могли его закрыть.

Не поймите меня неправильно... это все еще хак, который не должен быть реализован... но он немного чище с точки зрения использования памяти.

Ответ 2

Как alun, указанный выше, а также в этом вопросе вы можете подавать поток в другой 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 из-за взаимодействия сторонних библиотек с потоком.

Ответ 3

Я столкнулся с подобными проблемами с API, которые закрывают/удаляют потоки, которые они не владеют. Я не знаком с NPOI, но я предполагаю, что метод Write принимает Stream, а не MemoryStream. Если это так, вы можете создать класс Streamer Stream, который переадресует все вызовы (чтение/запись/поиск и т.д.) Во внутренний поток (ваш MemoryStream в этом случае), но не переадресовывает вызовы для закрытия/удаления. Передайте оболочку методу Write, когда он вернет ваш MemoryStream, должен содержать все содержимое и все еще быть "открытым".

Кроме того, вам, вероятно, понадобится ms.Seek(0, SeekOrigin.Begin). После того, как вызов Write your memory stream будет помещен в конец потока, поэтому, если вы попытаетесь прочитать с этой позиции, он появится emtpy.

Ответ 4

Я не знаю, если это все еще нужно, но есть overload

Write(Stream stream, bool leaveOpen)

где, если вы установите значение leaveOpen = true, leaveOpen = true ваш MemoryStream открытым