Skip to content

File(stream, ...) does not dispose the stream #60860

Open
@jhudsoncedaron

Description

@jhudsoncedaron

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Not sure if this is actually a bug or not, but it's a surprise. If this is intended behavior, it needs to be spelled out in the documentation because it violates expectations.

We have code that looks like this:

    [HttpPost]
    public IActionResult Run(ExportRequestModel request) {
        var result = this.ExportService.Export(request);
        string suffix = "." + result.DefaultExtension;
        if (request.FileName?.EndsWith(suffix) == true) {
            request.FileName = request.FileName[..^suffix.Length];
        }
        var name = (request.FileName ?? "export").Replace('.', '_') + suffix;
        return File(result.Contents, result.ContentType, name);
    }

However the stream from ExportService really does need to be disposed. There's a finalizer that will take care of it eventually, but that's unexpected. Making it work:

    [HttpPost]
    public IActionResult Run(ExportRequestModel request) {
        var result = this.ExportService.Export(request);
        string suffix = "." + result.DefaultExtension;
        if (request.FileName?.EndsWith(suffix) == true) {
            request.FileName = request.FileName[..^suffix.Length];
        }
        var name = (request.FileName ?? "export").Replace('.', '_') + suffix;
        HttpContext.Response.RegisterForDisposeAsync(result.Contents);
        return File(result.Contents, result.ContentType, name);
    }

Yup. File() doesn't dispose the stream. Surprise!

Expected Behavior

Option 1) File(stream, ...) disposes the stream

Option 2) Unexpected behavior documented at https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filestreamresult.-ctor?view=aspnetcore-9.0#microsoft-aspnetcore-mvc-filestreamresult-ctor(system-io-stream-system-string)

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

8.0.10

Anything else?

Hand verified by putting a breakpoint in the dispose method.

You don't have to worry about breaking me at least; I can handle being double-disposed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templates

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions