Nina Marszałek
Nina Marszałek

Reputation: 1

ASP .NET Cannot access a disposed stream

I'm trying to generate an excel file and then pass it to http response:

    [HttpPost("downloadCodesFile")]
    public async Task<IActionResult> DownloadExcelFile(GetSeriesExcelSheetRequest request)
    {
        var stream = new MemoryStream();
        using (var workbook = new XLWorkbook())
        {

            var codes = await _dataContext.Codes.Where(c => c.IsActive && c.SeriesNumber == request.SeriesNumber)
           .Select(entry => entry.CodeName).ToListAsync();

            var worksheet = workbook.Worksheets.Add("Sheet1");

            // Set cell values
            int i = 1;
            foreach (var code in codes)
            {
                worksheet.Cell($"A{i}").Value = code;
                i++;
            }

            workbook.SaveAs(stream);
            stream.Position = 0;

            return new FileStreamResult(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        };
    }

The file got generated once when I testes but then I keep getting the following error:

System.ObjectDisposedException: Cannot access a closed Stream.
   at System.IO.MemoryStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.MemoryStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Http.StreamCopyOperationInternal.CopyToAsync(Stream source, Stream destination, Nullable`1 count, Int32 bufferSize, CancellationToken cancel)
   at Microsoft.AspNetCore.Internal.FileResultHelper.WriteFileAsync(HttpContext context, Stream fileStream, RangeItemHeaderValue range, Int64 rangeLength)
   at Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase.WriteFileAsync(HttpContext context, Stream fileStream, RangeItemHeaderValue range, Int64 rangeLength)
   at Microsoft.AspNetCore.Mvc.Infrastructure.FileStreamResultExecutor.ExecuteAsync(ActionContext context, FileStreamResult result)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Been trying to figure out by adding stream to using block, without success

Upvotes: 0

Views: 327

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1063884

It looks like there's a glitch in XLWorkbook whereby SaveAs keeps hold of the supplied stream, and it looks like Dispose() is then hooked by Fody to dispose that.

This presents two options:

  1. remove the using in using (var workbook = new XLWorkbook()) - so you just have var workbook = new XLWorkbook(); - this is ... ugly and unsatisfying, because we don't know what else this is not disposing
  2. copy the data out, assuming that what we give to SaveAs is going to be burned

Consider:

Stream stream;
using (var workbook = new XLWorkbook())
{
    // ... write the content etc

    using var tmp = new MemoryStream(); // will be burned with workbook
    workbook.SaveAs(tmp);
    stream = new MemoryStream(tmp.GetBuffer(), 0, (int)tmp.Length);
};
return new FileStreamResult(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

This reuses the underlying buffer (no additional copies), but in a second MemoryStream instance that is not disposed.

Upvotes: 2

Related Questions