net mvc exportar create crear asp c# excel asp.net-web-api npoi

exportar - export to excel c# mvc



MemoryStream parece cerrarse después de NPOI workbook.write? (3)

Estoy usando NPOI para convertir DataTable a Excel en un proyecto de API web ASP.NET.

Pero no obtuve nada de la respuesta. Aquí está mi código:

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

Establecí un punto de interrupción para inspeccionar la ms.Length después del workbook.Write(ms) ms.Length workbook.Write(ms) , pero devuelve una excepción: System.ObjectDisposedException .

¿Qué hice mal?


Como se indicó anteriormente, y también en this pregunta, puede enviar el flujo a otro 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; }

Hay un pequeño olor a código de tener que hacer esto. Sin embargo, esto solo es necesario al generar archivos .xlsx debido a la forma en que las bibliotecas de terceros involucradas manejan la transmisión.


He encontrado problemas similares con las API que cierran / eliminan flujos que no son de su propiedad. No estoy familiarizado con NPOI, pero supongo que el método de escritura está aceptando Stream, no MemoryStream. Si ese es el caso, puede crear una clase de secuencia de contenedor que reenvíe todas las llamadas (lectura / escritura / búsqueda, etc.) a la secuencia interna (su MemoryStream en este caso), pero no reenvía las llamadas para cerrar / eliminar. Pase la envoltura al método de escritura, cuando devuelva su MemoryStream debe contener todo el contenido y aún estar "abierto".

Además, es probable que necesites hacer ms.Seek(0, SeekOrigin.Begin) . Después de la llamada a Write, su flujo de memoria se ubicará al final del flujo, por lo que si intenta leer desde esa posición aparecerá un mensaje vacío.


Otra solución a este problema ... que no utiliza varios objetos MemoryStream .

Cree una clase NpoiMemoryStream que herede MemoryStream y anule el método 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(); } }

Entonces, usa esa corriente como esta:

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

En algún punto entre la descarga y la búsqueda, NPOI intentará cerrar la secuencia, pero como anulamos Close() y el indicador AllowClose es falso, podemos mantener la secuencia abierta. Luego, establezca AllowClose en verdadero para que los mecanismos de eliminación normales puedan cerrarlo.

No me malinterpretes ... esto sigue siendo un truco que no debería ser implementado ... pero es un poco más limpio desde el punto de vista del uso de la memoria.